From 61f485ea3f96044ea341cba9bdd17fb149b00fe3 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 17 Jul 2020 10:07:03 -0400 Subject: [PATCH 001/130] Initial commit sketching out ideas --- .gitignore | 2 + Cargo.toml | 15 ++++ src/lib.rs | 154 +++++++++++++++++++++++++++++++++++++ src/state_event.rs | 49 ++++++++++++ src/state_store.rs | 26 +++++++ state.md | 20 +++++ tests/init.rs | 184 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 450 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/state_event.rs create mode 100644 src/state_store.rs create mode 100644 state.md create mode 100644 tests/init.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..5f77a127 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "state-res" +version = "0.1.0" +authors = ["Devin R "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +petgraph = "0.5.1" +serde = { version = "1.0.114", features = ["derive"] } +serde_json = "1.0.56" + +[dependencies.ruma] +git = "https://github.com/ruma/ruma" +features = ["client-api", "federation-api", "appservice-api"] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..fa2a6c50 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,154 @@ +use std::{collections::BTreeMap, time::SystemTime}; + +use petgraph::Graph; +use ruma::{ + events::{ + room::{self}, + AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId}, +}; +use serde::{Deserialize, Serialize}; + +mod state_event; +mod state_store; + +pub use state_event::StateEvent; +pub use state_store::StateStore; + +pub enum ResolutionResult { + Conflicted(Vec>), + Resolved(Vec>), +} + +/// A mapping of event type and state_key to some value `T`, usually an `EventId`. +pub type StateMap = BTreeMap<(EventType, String), T>; + +/// A mapping of `EventId` to `T`, usually a `StateEvent`. +pub type EventMap = BTreeMap; + +#[derive(Debug, Default, Deserialize, Serialize)] // TODO make the ser/de impls useful +pub struct StateResolution { + // TODO remove pub after initial testing + /// The set of resolved events over time. + pub resolved_events: Vec, + /// The resolved state, kept to have easy access to the last resolved + /// layer of state. + pub state: BTreeMap>, + /// The graph of authenticated events, kept to find the most recent auth event + /// in a chain for incoming state sets. + pub auth_graph: BTreeMap>>, + /// The last known point in the state graph. + pub most_recent_resolved: Option<(EventType, String)>, + + // fields for temp storage during resolution + pub conflicting_events: Vec, +} + +impl StateResolution { + /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph + /// and an auth chain to allow for state conflict resolution. + pub fn resolve( + &mut self, + room_id: &RoomId, + room_version: &RoomVersionId, + state_sets: Vec>, + store: &mut dyn StateStore, + // TODO actual error handling (`thiserror`??) + ) -> Result { + let mut event_map = EventMap::new(); + // split non-conflicting and conflicting state + let (clean, mut conflicting) = self.seperate(&state_sets); + + if conflicting.is_empty() { + return Ok(ResolutionResult::Resolved(clean)); + } + + // the set of auth events that are not common across server forks + let mut auth_diff = self.get_auth_chain_diff(&state_sets, &mut event_map, store)?; + + // add the auth_diff to conflicting now we have a full set of conflicting events + auth_diff.extend(conflicting.iter().flat_map(|map| map.values().cloned())); + let all_conflicted = auth_diff; + + let all_conflicted = conflicting; + + let power_events = all_conflicted + .iter() + .filter(is_power_event) + .flat_map(|map| map.values()) + .cloned() + .collect::>(); + + // sort the power events based on power_level/clock/event_id and outgoing/incoming edges + let sorted_power_levels = self.revers_topological_power_sort( + room_id, + &power_events, + &mut event_map, + store, + &all_conflicted, + ); + + // sequentially auth check each event. + let resolved = self.iterative_auth_check( + room_id, + room_version, + &power_events, + &clean, + &mut event_map, + store, + ); + + // TODO return something not a place holder + Ok(ResolutionResult::Resolved(vec![])) + } + + fn seperate( + &mut self, + state_sets: &[StateMap], + ) -> (Vec>, Vec>) { + panic!() + } + + /// Returns a Vec of deduped EventIds that appear in some chains but no others. + fn get_auth_chain_diff( + &mut self, + state_sets: &[StateMap], + event_map: &EventMap, + store: &mut dyn StateStore, + ) -> Result, serde_json::Error> { + panic!() + } + + fn revers_topological_power_sort( + &mut self, + room_id: &RoomId, + power_events: &[EventId], + event_map: &EventMap, + store: &mut dyn StateStore, + conflicted_set: &[StateMap], + ) -> Vec { + panic!() + } + + fn iterative_auth_check( + &mut self, + room_id: &RoomId, + room_version: &RoomVersionId, + power_events: &[EventId], + unconflicted_state: &[StateMap], + event_map: &EventMap, + store: &mut dyn StateStore, + ) -> Vec { + panic!() + } +} + +pub fn is_power_event(event: &&StateMap) -> bool { + true +} + +#[cfg(test)] +mod tests { + use super::*; +} diff --git a/src/state_event.rs b/src/state_event.rs new file mode 100644 index 00000000..a42c1109 --- /dev/null +++ b/src/state_event.rs @@ -0,0 +1,49 @@ +use ruma::events::{ + from_raw_json_value, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper, +}; +use serde::{de, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum StateEvent { + Full(AnyStateEvent), + Sync(AnySyncStateEvent), + Stripped(AnyStrippedStateEvent), +} + +impl<'de> de::Deserialize<'de> for StateEvent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { + state_key, + event_id, + room_id, + unsigned, + .. + } = from_raw_json_value(&json)?; + + // Determine whether the event is a full, sync, or stripped + // based on the fields present. + if room_id.is_some() { + Ok(match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Full(from_raw_json_value(&json)?), + }) + } else if event_id.is_some() { + Ok(match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Sync(from_raw_json_value(&json)?), + }) + } else { + Ok(StateEvent::Stripped(from_raw_json_value(&json)?)) + } + } +} diff --git a/src/state_store.rs b/src/state_store.rs new file mode 100644 index 00000000..f84e8127 --- /dev/null +++ b/src/state_store.rs @@ -0,0 +1,26 @@ +use std::{collections::BTreeMap, time::SystemTime}; + +use petgraph::Graph; +use ruma::{ + events::{ + room::{self}, + AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId}, +}; + +use crate::StateEvent; + +pub trait StateStore { + /// Returns the events that correspond to the `event_ids` sorted in the same order. + fn get_events(&self, event_ids: &[EventId]) -> Result, serde_json::Error>; + + /// 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( + &self, + room_id: &RoomId, + version: &RoomVersionId, + event_id: &EventId, + ) -> Result<(Vec, Vec), serde_json::Error>; +} diff --git a/state.md b/state.md new file mode 100644 index 00000000..cc5e8575 --- /dev/null +++ b/state.md @@ -0,0 +1,20 @@ +Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of +```rust +// The would need to be Serialize/Deserialize to save state +struct StateResV2 { + resolved_events: Vec, + state_graph: of indexes into the events field?, + most_recent_resolved: index or ptr into the graph?, + // fields for temp storage during resolution + conflicting_events: Vec, +} + +impl StateResV2 { + /// The point of this all add nonconflicting events to the graph + /// and resolve and add conflicting events. + fn resolve(&mut self, events: Vec) -> Vec { } + +} + +``` +Now to be totally fair I have no real understanding of state res diff --git a/tests/init.rs b/tests/init.rs new file mode 100644 index 00000000..93efbc12 --- /dev/null +++ b/tests/init.rs @@ -0,0 +1,184 @@ +use std::convert::TryFrom; + +use ruma::{ + events::{ + room::{self}, + AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId}, +}; +use serde_json::{from_value as from_json_value, json, Value as JsonValue}; +use state_res::{ResolutionResult, StateEvent, StateResolution, StateStore}; + +// TODO make this an array of events +fn federated_json() -> JsonValue { + json!({ + "content": { + "creator": "@example:example.org", + "m.federate": true, + "predecessor": { + "event_id": "$something:example.org", + "room_id": "!oldroom:example.org" + }, + "room_version": "6" + }, + "event_id": "$aaa:example.org", + "origin_server_ts": 1, + "room_id": "!room_id:example.org", + "sender": "@alice:example.org", + "state_key": "", + "type": "m.room.create", + "unsigned": { + "age": 1234 + } + }) +} + +fn room_create() -> JsonValue { + json!({ + "content": { + "creator": "@example:example.org", + "m.federate": true, + "predecessor": { + "event_id": "$something:example.org", + "room_id": "!oldroom:example.org" + }, + "room_version": "6" + }, + "event_id": "$aaa:example.org", + "origin_server_ts": 1, + "room_id": "!room_id:example.org", + "sender": "@alice:example.org", + "state_key": "", + "type": "m.room.create", + "unsigned": { + "age": 1234 + } + }) +} + +fn join_rules() -> JsonValue { + json!({ + "content": { + "join_rule": "public" + }, + "event_id": "$bbb:example.org", + "origin_server_ts": 2, + "room_id": "!room_id:example.org", + "sender": "@alice:example.org", + "state_key": "", + "type": "m.room.join_rules", + "unsigned": { + "age": 1234 + } + }) +} + +fn join_event() -> JsonValue { + json!({ + "content": { + "avatar_url": null, + "displayname": "example", + "membership": "join" + }, + "event_id": "$ccc:example.org", + "membership": "join", + "room_id": "!room_id:example.org", + "origin_server_ts": 3, + "sender": "@alice:example.org", + "state_key": "@alice:example.org", + "type": "m.room.member", + "unsigned": { + "age": 1, + "replaces_state": "$151800111315tsynI:example.org", + "prev_content": { + "avatar_url": null, + "displayname": "example", + "membership": "invite" + } + } + }) +} + +fn power_levels() -> JsonValue { + json!({ + "content": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": { + "room": 20 + }, + "redact": 50, + "state_default": 50, + "users": { + "@example:example.org": 100 + }, + "users_default": 0 + }, + "event_id": "$ddd:example.org", + "origin_server_ts": 4, + "room_id": "!room_id:example.org", + "sender": "@example:example.org", + "state_key": "", + "type": "m.room.power_levels", + "unsigned": { + "age": 1234 + } + }) +} + +pub struct TestStore; + +impl StateStore for TestStore { + fn get_events(&self, events: &[EventId]) -> Result, serde_json::Error> { + Ok(vec![from_json_value(power_levels())?]) + } + + fn get_remote_state_for_room( + &self, + room_id: &RoomId, + version: &RoomVersionId, + event_id: &EventId, + ) -> Result<(Vec, Vec), serde_json::Error> { + Ok(( + vec![from_json_value(federated_json())?], + vec![from_json_value(power_levels())?], + )) + } +} + +#[test] +fn it_works() { + let mut store = TestStore; + + let room_id = RoomId::try_from("!room_id:example.org").unwrap(); + let room_version = RoomVersionId::version_6(); + + let a = from_json_value::(room_create()).unwrap(); + let b = from_json_value::(join_rules()).unwrap(); + let c = from_json_value::(join_event()).unwrap(); + + let mut resolver = StateResolution::default(); + + let res = resolver + .resolve(&room_id, &room_version, vec![a.clone()], &mut store) + .unwrap(); + assert!(if let ResolutionResult::Resolved(_) = res { + true + } else { + false + }); + + let resolved = resolver + .resolve(&room_id, &room_version, vec![b, c], &mut store) + .unwrap(); + + assert!(resolver.conflicting_events.is_empty()); + assert_eq!(resolver.resolved_events.len(), 3); +} From c043b10d64e1481d36412cd0a39d0b544c7ffaa6 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 18 Jul 2020 14:01:22 -0400 Subject: [PATCH 002/130] Sketch is mostly finalized now fill in with code --- Cargo.toml | 1 + src/lib.rs | 187 ++++++++++++++++++++++++++++++++++++++++----- src/state_event.rs | 77 ++++++++++++++++++- src/state_store.rs | 10 ++- tests/init.rs | 42 +++++++--- 5 files changed, 284 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f77a127..fce5f776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" petgraph = "0.5.1" serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.56" +maplit = "1.0.2" [dependencies.ruma] git = "https://github.com/ruma/ruma" diff --git a/src/lib.rs b/src/lib.rs index fa2a6c50..1fea5903 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,14 @@ mod state_store; pub use state_event::StateEvent; pub use state_store::StateStore; +// We want to yield to the reactor occasionally during state res when dealing +// with large data sets, so that we don't exhaust the reactor. This is done by +// yielding to reactor during loops every N iterations. +const _YIELD_AFTER_ITERATIONS: usize = 100; + pub enum ResolutionResult { Conflicted(Vec>), - Resolved(Vec>), + Resolved(StateMap), } /// A mapping of event type and state_key to some value `T`, usually an `EventId`. @@ -53,15 +58,17 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: Vec>, - store: &mut dyn StateStore, + store: &dyn StateStore, // TODO actual error handling (`thiserror`??) ) -> Result { let mut event_map = EventMap::new(); // split non-conflicting and conflicting state - let (clean, mut conflicting) = self.seperate(&state_sets); + let (clean, conflicting) = self.seperate(&state_sets); if conflicting.is_empty() { - return Ok(ResolutionResult::Resolved(clean)); + return Ok(ResolutionResult::Resolved( + clean.into_iter().flat_map(|map| map.into_iter()).collect(), + )); } // the set of auth events that are not common across server forks @@ -71,17 +78,18 @@ impl StateResolution { auth_diff.extend(conflicting.iter().flat_map(|map| map.values().cloned())); let all_conflicted = auth_diff; - let all_conflicted = conflicting; + // TODO get events and add to event_map + // 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}` let power_events = all_conflicted .iter() - .filter(is_power_event) - .flat_map(|map| map.values()) + .filter(|id| is_power_event(id, store)) .cloned() .collect::>(); // sort the power events based on power_level/clock/event_id and outgoing/incoming edges - let sorted_power_levels = self.revers_topological_power_sort( + let mut sorted_power_levels = self.revers_topological_power_sort( room_id, &power_events, &mut event_map, @@ -93,16 +101,48 @@ impl StateResolution { let resolved = self.iterative_auth_check( room_id, room_version, - &power_events, + &sorted_power_levels, &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; + + let events_to_resolve = all_conflicted + .iter() + .filter(|id| deduped_power_ev.contains(id)) + .cloned() + .collect::>(); + + let power_event = resolved.get(&(EventType::RoomPowerLevels, "".into())); + + let sorted_left_events = + self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); + + let mut resolved_state = self.iterative_auth_check( + room_id, + room_version, + &sorted_left_events, + &[resolved], + &mut event_map, + store, + ); + + // add unconflicted state to the resolved state + resolved_state.extend(clean.into_iter().flat_map(|map| map.into_iter())); + // TODO return something not a place holder - Ok(ResolutionResult::Resolved(vec![])) + Ok(ResolutionResult::Resolved(resolved_state)) } + /// Split the events that have no conflicts from those that are conflicting. + /// + /// The tuple looks like `(unconflicted, conflicted)`. fn seperate( &mut self, state_sets: &[StateMap], @@ -115,7 +155,7 @@ impl StateResolution { &mut self, state_sets: &[StateMap], event_map: &EventMap, - store: &mut dyn StateStore, + store: &dyn StateStore, ) -> Result, serde_json::Error> { panic!() } @@ -125,9 +165,9 @@ impl StateResolution { room_id: &RoomId, power_events: &[EventId], event_map: &EventMap, - store: &mut dyn StateStore, - conflicted_set: &[StateMap], - ) -> Vec { + store: &dyn StateStore, + conflicted_set: &[EventId], + ) -> Vec { panic!() } @@ -138,14 +178,125 @@ impl StateResolution { power_events: &[EventId], unconflicted_state: &[StateMap], event_map: &EventMap, - store: &mut dyn StateStore, - ) -> Vec { + store: &dyn StateStore, + ) -> StateMap { panic!() } + + /// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using + /// the `resolved_power_level`. + fn mainline_sort( + &mut self, + room_id: &RoomId, + to_sort: &[EventId], + resolved_power_level: Option<&EventId>, + event_map: &EventMap, + store: &dyn StateStore, + ) -> Vec { + // There can be no EventId's to sort, bail. + if to_sort.is_empty() { + return vec![]; + } + + let mut mainline = vec![]; + let mut pl = resolved_power_level.cloned(); + let mut idx = 0; + while let Some(p) = pl { + mainline.push(p.clone()); + // We don't need the actual pl_ev here since we delegate to the store + let auth_events = store.auth_event_ids(room_id, &p).unwrap(); + pl = None; + for aid in auth_events { + let ev = store.get_event(&aid).unwrap(); + if ev.is_type_and_key(EventType::RoomPowerLevels, "") { + pl = Some(aid); + break; + } + } + // 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 != 0 && idx % _YIELD_AFTER_ITERATIONS == 0 { + // yield clock.sleep(0) + } + idx += 1; + } + + let mainline_map = mainline + .iter() + .enumerate() + .map(|(idx, eid)| ((*eid).clone(), idx)) + .collect::>(); + let mut sort_event_ids = to_sort.to_vec(); + + let mut order_map = BTreeMap::new(); + for (idx, ev_id) in to_sort.iter().enumerate() { + let depth = self.get_mainline_depth( + room_id, + event_map.get(ev_id).cloned(), + &mainline_map, + store, + ); + order_map.insert( + ev_id, + ( + depth, + event_map.get(ev_id).map(|ev| ev.origin_server_ts()), + ev_id, // TODO should this be a &str to sort lexically?? + ), + ); + + // 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) + } + } + + // sort the event_ids by their depth, timestamp and EventId + sort_event_ids.sort_by_key(|sort_id| order_map.get(sort_id).unwrap()); + + sort_event_ids + } + + fn get_mainline_depth( + &mut self, + room_id: &RoomId, + mut event: Option, + mainline_map: &EventMap, + store: &dyn StateStore, + ) -> usize { + while let Some(sort_ev) = event { + if let Some(id) = sort_ev.event_id() { + if let Some(depth) = mainline_map.get(id) { + return *depth; + } + } + + let auth_events = if let Some(id) = sort_ev.event_id() { + store.auth_event_ids(room_id, id).unwrap() + } else { + vec![] + }; + event = None; + + for aid in auth_events { + let aev = store.get_event(&aid).unwrap(); + if aev.is_type_and_key(EventType::RoomPowerLevels, "") { + event = Some(aev); + break; + } + } + } + // Did not find a power level event so we default to zero + 0 + } } -pub fn is_power_event(event: &&StateMap) -> bool { - true +pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool { + match store.get_event(event_id) { + Ok(state) => state.is_power_event(), + _ => false, // TODO this shouldn't eat errors + } } #[cfg(test)] diff --git a/src/state_event.rs b/src/state_event.rs index a42c1109..33954df4 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,8 +1,13 @@ -use ruma::events::{ - from_raw_json_value, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper, +use ruma::{ + events::{ + from_raw_json_value, room::member::MembershipState, AnyStateEvent, AnyStrippedStateEvent, + AnySyncStateEvent, EventDeHelper, EventType, + }, + identifiers::{EventId, RoomId}, }; use serde::{de, Serialize}; use serde_json::value::RawValue as RawJsonValue; +use std::{convert::TryFrom, time::SystemTime}; #[derive(Clone, Debug, Serialize)] #[serde(untagged)] @@ -12,6 +17,74 @@ pub enum StateEvent { Stripped(AnyStrippedStateEvent), } +impl StateEvent { + pub fn is_power_event(&self) -> bool { + match self { + Self::Full(any_event) => match any_event { + AnyStateEvent::RoomPowerLevels(event) => event.state_key == "", + AnyStateEvent::RoomJoinRules(event) => event.state_key == "", + AnyStateEvent::RoomCreate(event) => event.state_key == "", + AnyStateEvent::RoomMember(event) => { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&event.content.membership) + { + return event.sender.as_str() != event.state_key; + } + false + } + _ => false, + }, + Self::Sync(any_event) => match any_event { + AnySyncStateEvent::RoomPowerLevels(event) => event.state_key == "", + AnySyncStateEvent::RoomJoinRules(event) => event.state_key == "", + AnySyncStateEvent::RoomCreate(event) => event.state_key == "", + AnySyncStateEvent::RoomMember(event) => { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&event.content.membership) + { + return event.sender.as_str() != event.state_key; + } + false + } + _ => false, + }, + Self::Stripped(any_event) => match any_event { + AnyStrippedStateEvent::RoomPowerLevels(event) => event.state_key == "", + AnyStrippedStateEvent::RoomJoinRules(event) => event.state_key == "", + AnyStrippedStateEvent::RoomCreate(event) => event.state_key == "", + AnyStrippedStateEvent::RoomMember(event) => { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&event.content.membership) + { + return event.sender.as_str() != event.state_key; + } + false + } + _ => false, + }, + _ => false, + } + } + pub fn origin_server_ts(&self) -> Option<&SystemTime> { + match self { + Self::Full(ev) => Some(ev.origin_server_ts()), + Self::Sync(ev) => Some(ev.origin_server_ts()), + Self::Stripped(ev) => None, + } + } + pub fn event_id(&self) -> Option<&EventId> { + match self { + Self::Full(ev) => Some(ev.event_id()), + Self::Sync(ev) => Some(ev.event_id()), + Self::Stripped(ev) => None, + } + } + + pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { + true + } +} + impl<'de> de::Deserialize<'de> for StateEvent { fn deserialize(deserializer: D) -> Result where diff --git a/src/state_store.rs b/src/state_store.rs index f84e8127..d5c7c862 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -12,8 +12,14 @@ use ruma::{ use crate::StateEvent; pub trait StateStore { + /// Return a single event based on the EventId. + fn get_event(&self, event_id: &EventId) -> Result; + /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, event_ids: &[EventId]) -> Result, serde_json::Error>; + fn get_events(&self, event_ids: &[EventId]) -> Result, String>; + + /// Returns a Vec of the related auth events to the given `event`. + fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String>; /// Returns a tuple of requested state events from `event_id` and the auth chain events that /// relate to the. @@ -22,5 +28,5 @@ pub trait StateStore { room_id: &RoomId, version: &RoomVersionId, event_id: &EventId, - ) -> Result<(Vec, Vec), serde_json::Error>; + ) -> Result<(Vec, Vec), String>; } diff --git a/tests/init.rs b/tests/init.rs index 93efbc12..88c64260 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -1,5 +1,6 @@ -use std::convert::TryFrom; +use std::{collections::BTreeMap, convert::TryFrom}; +use maplit::btreemap; use ruma::{ events::{ room::{self}, @@ -136,8 +137,22 @@ fn power_levels() -> JsonValue { pub struct TestStore; impl StateStore for TestStore { - fn get_events(&self, events: &[EventId]) -> Result, serde_json::Error> { - Ok(vec![from_json_value(power_levels())?]) + fn get_events(&self, events: &[EventId]) -> Result, String> { + vec![room_create(), join_rules(), join_event(), power_levels()] + .into_iter() + .map(from_json_value) + .collect::>>() + .map_err(|e| e.to_string()) + } + + fn get_event(&self, event_id: &EventId) -> Result { + from_json_value(power_levels()).map_err(|e| e.to_string()) + } + + fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String> { + Ok(vec![ + EventId::try_from("$aaa:example.org").map_err(|e| e.to_string())? + ]) } fn get_remote_state_for_room( @@ -145,10 +160,10 @@ impl StateStore for TestStore { room_id: &RoomId, version: &RoomVersionId, event_id: &EventId, - ) -> Result<(Vec, Vec), serde_json::Error> { + ) -> Result<(Vec, Vec), String> { Ok(( - vec![from_json_value(federated_json())?], - vec![from_json_value(power_levels())?], + vec![from_json_value(federated_json()).map_err(|e| e.to_string())?], + vec![from_json_value(power_levels()).map_err(|e| e.to_string())?], )) } } @@ -160,14 +175,18 @@ fn it_works() { let room_id = RoomId::try_from("!room_id:example.org").unwrap(); let room_version = RoomVersionId::version_6(); - let a = from_json_value::(room_create()).unwrap(); - let b = from_json_value::(join_rules()).unwrap(); - let c = from_json_value::(join_event()).unwrap(); + let initial_state = btreemap! { + (EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(), + }; + + let state_to_resolve = btreemap! { + (EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(), + }; let mut resolver = StateResolution::default(); let res = resolver - .resolve(&room_id, &room_version, vec![a.clone()], &mut store) + .resolve(&room_id, &room_version, vec![initial_state], &mut store) .unwrap(); assert!(if let ResolutionResult::Resolved(_) = res { true @@ -176,9 +195,10 @@ fn it_works() { }); let resolved = resolver - .resolve(&room_id, &room_version, vec![b, c], &mut store) + .resolve(&room_id, &room_version, vec![state_to_resolve], &mut store) .unwrap(); assert!(resolver.conflicting_events.is_empty()); assert_eq!(resolver.resolved_events.len(), 3); + assert_eq!(resolver.resolved_events.len(), 3); } From d3c3c9598521c80e4a256d96f4b4ba1717316467 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 18 Jul 2020 16:14:57 -0400 Subject: [PATCH 003/130] Use ruma's PDU events not the sent events --- Cargo.toml | 5 +- README.md | 38 ++++++++++++++ src/lib.rs | 75 ++++++++++++++++++++++----- src/state_event.rs | 126 ++++++++++++++++++++++++++------------------- state.md | 20 ------- tests/init.rs | 4 +- 6 files changed, 179 insertions(+), 89 deletions(-) create mode 100644 README.md delete mode 100644 state.md diff --git a/Cargo.toml b/Cargo.toml index fce5f776..e72bb7e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,11 @@ edition = "2018" petgraph = "0.5.1" serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.56" -maplit = "1.0.2" +tracing = "0.1.16" [dependencies.ruma] git = "https://github.com/ruma/ruma" features = ["client-api", "federation-api", "appservice-api"] + +[dev-dependencies] +maplit = "1.0.2" diff --git a/README.md b/README.md new file mode 100644 index 00000000..38e64375 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of +```rust +// The would need to be Serialize/Deserialize to save state +struct StateResV2 { + // Should any information be kept or should all of it be fetched from the + // StateStore trait?, + state_graph: Something, + + // fields for temp storage during resolution?? + conflicting_events: StateMap>, +} + +impl StateResV2 { + /// The point of this all add nonconflicting events to the graph + /// and resolve and add conflicting events. + fn resolve(&mut self, events: Vec>) -> StateMap { } + +} + +// The tricky part to making this a good abstraction +trait StateStore { + fn get_event(&self, event_id: &EventId) -> Pdu/AnyStateEvent; + + fn get_events(&self, event_ids: &[EventId]) -> Pdu/AnyStateEvent; + + fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Vec; + + fn get_remote_state_for_room( + &self, + room_id: &RoomId, + version: &RoomVersionId, + event_id: &EventId, + ) -> (Vec, Vec); + +} + +``` +Now to be totally fair I have no real understanding of state reso diff --git a/src/lib.rs b/src/lib.rs index 1fea5903..49e31546 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,28 +57,55 @@ impl StateResolution { &mut self, room_id: &RoomId, room_version: &RoomVersionId, - state_sets: Vec>, + state_sets: &[StateMap], store: &dyn StateStore, // TODO actual error handling (`thiserror`??) - ) -> Result { + ) -> Result { + tracing::debug!("State resolution starting"); + let mut event_map = EventMap::new(); // split non-conflicting and conflicting state let (clean, conflicting) = self.seperate(&state_sets); if conflicting.is_empty() { - return Ok(ResolutionResult::Resolved( - clean.into_iter().flat_map(|map| map.into_iter()).collect(), - )); + return Ok(ResolutionResult::Resolved(clean)); } + tracing::debug!("computing {} conflicting events", conflicting.len()); + // the set of auth events that are not common across server forks let mut auth_diff = self.get_auth_chain_diff(&state_sets, &mut event_map, store)?; // add the auth_diff to conflicting now we have a full set of conflicting events - auth_diff.extend(conflicting.iter().flat_map(|map| map.values().cloned())); + auth_diff.extend(conflicting.values().cloned().flatten()); let all_conflicted = auth_diff; - // TODO get events and add to event_map + tracing::debug!("full conflicted set is {} events", all_conflicted.len()); + + let events = store + .get_events( + &all_conflicted + .iter() + // we only want the events we don't know about yet + .filter(|id| !event_map.contains_key(id)) + .cloned() + .collect::>(), + ) + .unwrap(); + + for event in event_map.values() { + if event.room_id() != Some(room_id) { + return Err(format!( + "resolving event {} in room {}, when correct room is {}", + event + .event_id() + .map(|id| id.as_str()) + .unwrap_or("`unknown`"), + event.room_id().map(|id| id.as_str()).unwrap_or("`unknown`"), + room_id.as_str() + )); + } + } // 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}` @@ -109,7 +136,6 @@ impl StateResolution { // 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; @@ -128,13 +154,13 @@ impl StateResolution { room_id, room_version, &sorted_left_events, - &[resolved], + &resolved, &mut event_map, store, ); // add unconflicted state to the resolved state - resolved_state.extend(clean.into_iter().flat_map(|map| map.into_iter())); + resolved_state.extend(clean); // TODO return something not a place holder Ok(ResolutionResult::Resolved(resolved_state)) @@ -146,8 +172,25 @@ impl StateResolution { fn seperate( &mut self, state_sets: &[StateMap], - ) -> (Vec>, Vec>) { - panic!() + ) -> (StateMap, StateMap>) { + let mut unconflicted_state = StateMap::new(); + let mut conflicted_state = StateMap::new(); + + for key in state_sets.iter().flat_map(|map| map.keys()) { + let mut event_ids = state_sets + .iter() + .flat_map(|map| map.get(key).cloned()) + .collect::>(); + + if event_ids.len() == 1 { + // unwrap is ok since we know the len is 1 + unconflicted_state.insert(key.clone(), event_ids.pop().unwrap()); + } else { + conflicted_state.insert(key.clone(), event_ids); + } + } + + (unconflicted_state, conflicted_state) } /// Returns a Vec of deduped EventIds that appear in some chains but no others. @@ -156,7 +199,8 @@ impl StateResolution { state_sets: &[StateMap], event_map: &EventMap, store: &dyn StateStore, - ) -> Result, serde_json::Error> { + ) -> Result, String> { + tracing::debug!("calculating auth chain difference"); panic!() } @@ -168,6 +212,7 @@ impl StateResolution { store: &dyn StateStore, conflicted_set: &[EventId], ) -> Vec { + tracing::debug!("reverse topological sort of power events"); panic!() } @@ -176,10 +221,11 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, power_events: &[EventId], - unconflicted_state: &[StateMap], + unconflicted_state: &StateMap, event_map: &EventMap, store: &dyn StateStore, ) -> StateMap { + tracing::debug!("starting iter auth check"); panic!() } @@ -193,6 +239,7 @@ impl StateResolution { event_map: &EventMap, store: &dyn StateStore, ) -> Vec { + tracing::debug!("mainline sort of remaining events"); // There can be no EventId's to sort, bail. if to_sort.is_empty() { return vec![]; diff --git a/src/state_event.rs b/src/state_event.rs index 33954df4..3fe07b4b 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,7 +1,9 @@ use ruma::{ events::{ - from_raw_json_value, room::member::MembershipState, AnyStateEvent, AnyStrippedStateEvent, - AnySyncStateEvent, EventDeHelper, EventType, + from_raw_json_value, + pdu::{Pdu, PduStub, RoomV1Pdu, RoomV1PduStub, RoomV3Pdu, RoomV3PduStub}, + room::member::{MemberEventContent, MembershipState}, + AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper, EventType, }, identifiers::{EventId, RoomId}, }; @@ -12,71 +14,93 @@ use std::{convert::TryFrom, time::SystemTime}; #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum StateEvent { - Full(AnyStateEvent), - Sync(AnySyncStateEvent), - Stripped(AnyStrippedStateEvent), + Full(Pdu), + Sync(PduStub), } impl StateEvent { pub fn is_power_event(&self) -> bool { match self { Self::Full(any_event) => match any_event { - AnyStateEvent::RoomPowerLevels(event) => event.state_key == "", - AnyStateEvent::RoomJoinRules(event) => event.state_key == "", - AnyStateEvent::RoomCreate(event) => event.state_key == "", - AnyStateEvent::RoomMember(event) => { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&event.content.membership) - { - return event.sender.as_str() != event.state_key; + Pdu::RoomV1Pdu(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + if let Ok(content) = + // TODO fix clone + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) + { + return event.sender.as_str() + // TODO does None here mean the same as state_key = "" + != event.state_key.as_deref().unwrap_or(""); + } + } + + false } - false - } - _ => false, + _ => false, + }, + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), }, Self::Sync(any_event) => match any_event { - AnySyncStateEvent::RoomPowerLevels(event) => event.state_key == "", - AnySyncStateEvent::RoomJoinRules(event) => event.state_key == "", - AnySyncStateEvent::RoomCreate(event) => event.state_key == "", - AnySyncStateEvent::RoomMember(event) => { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&event.content.membership) - { - return event.sender.as_str() != event.state_key; + PduStub::RoomV1PduStub(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + if let Ok(content) = + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) + { + return event.sender.as_str() + // TODO does None here mean the same as state_key = "" + != event.state_key.as_deref().unwrap_or(""); + } + } + + false } - false - } - _ => false, + _ => false, + }, + PduStub::RoomV3PduStub(event) => event.state_key == Some("".into()), }, - Self::Stripped(any_event) => match any_event { - AnyStrippedStateEvent::RoomPowerLevels(event) => event.state_key == "", - AnyStrippedStateEvent::RoomJoinRules(event) => event.state_key == "", - AnyStrippedStateEvent::RoomCreate(event) => event.state_key == "", - AnyStrippedStateEvent::RoomMember(event) => { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&event.content.membership) - { - return event.sender.as_str() != event.state_key; - } - false - } - _ => false, - }, - _ => false, } } - pub fn origin_server_ts(&self) -> Option<&SystemTime> { + pub fn origin_server_ts(&self) -> &SystemTime { match self { - Self::Full(ev) => Some(ev.origin_server_ts()), - Self::Sync(ev) => Some(ev.origin_server_ts()), - Self::Stripped(ev) => None, + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, + Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => &ev.origin_server_ts, + PduStub::RoomV3PduStub(ev) => &ev.origin_server_ts, + }, } } pub fn event_id(&self) -> Option<&EventId> { match self { - Self::Full(ev) => Some(ev.event_id()), - Self::Sync(ev) => Some(ev.event_id()), - Self::Stripped(ev) => None, + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => Some(&ev.event_id), + Pdu::RoomV3Pdu(ev) => None, + }, + Self::Sync(ev) => None, + } + } + + pub fn room_id(&self) -> Option<&RoomId> { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => Some(&ev.room_id), + Pdu::RoomV3Pdu(ev) => Some(&ev.room_id), + }, + Self::Sync(ev) => None, } } @@ -108,15 +132,13 @@ impl<'de> de::Deserialize<'de> for StateEvent { } _ => StateEvent::Full(from_raw_json_value(&json)?), }) - } else if event_id.is_some() { + } else { Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { panic!("TODO deal with redacted events") } _ => StateEvent::Sync(from_raw_json_value(&json)?), }) - } else { - Ok(StateEvent::Stripped(from_raw_json_value(&json)?)) } } } diff --git a/state.md b/state.md deleted file mode 100644 index cc5e8575..00000000 --- a/state.md +++ /dev/null @@ -1,20 +0,0 @@ -Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of -```rust -// The would need to be Serialize/Deserialize to save state -struct StateResV2 { - resolved_events: Vec, - state_graph: of indexes into the events field?, - most_recent_resolved: index or ptr into the graph?, - // fields for temp storage during resolution - conflicting_events: Vec, -} - -impl StateResV2 { - /// The point of this all add nonconflicting events to the graph - /// and resolve and add conflicting events. - fn resolve(&mut self, events: Vec) -> Vec { } - -} - -``` -Now to be totally fair I have no real understanding of state res diff --git a/tests/init.rs b/tests/init.rs index 88c64260..a3c4936b 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -186,7 +186,7 @@ fn it_works() { let mut resolver = StateResolution::default(); let res = resolver - .resolve(&room_id, &room_version, vec![initial_state], &mut store) + .resolve(&room_id, &room_version, &[initial_state], &mut store) .unwrap(); assert!(if let ResolutionResult::Resolved(_) = res { true @@ -195,7 +195,7 @@ fn it_works() { }); let resolved = resolver - .resolve(&room_id, &room_version, vec![state_to_resolve], &mut store) + .resolve(&room_id, &room_version, &[state_to_resolve], &mut store) .unwrap(); assert!(resolver.conflicting_events.is_empty()); From b2cbc3cd5d7a8ca40db438ecdeb92eb422b1a0f2 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 18 Jul 2020 16:18:44 -0400 Subject: [PATCH 004/130] Update readme for repo --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 38e64375..759c0565 100644 --- a/README.md +++ b/README.md @@ -17,22 +17,26 @@ impl StateResV2 { } -// The tricky part to making this a good abstraction +// The tricky part of making a good abstraction trait StateStore { - fn get_event(&self, event_id: &EventId) -> Pdu/AnyStateEvent; + /// Return a single event based on the EventId. + fn get_event(&self, event_id: &EventId) -> Result; - fn get_events(&self, event_ids: &[EventId]) -> Pdu/AnyStateEvent; + /// Returns the events that correspond to the `event_ids` sorted in the same order. + fn get_events(&self, event_ids: &[EventId]) -> Result, String>; - fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Vec; + /// Returns a Vec of the related auth events to the given `event`. + fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String>; + /// Returns a tuple of requested state events from `event_id` and the auth chain events that + /// they relate to the. fn get_remote_state_for_room( &self, room_id: &RoomId, version: &RoomVersionId, event_id: &EventId, - ) -> (Vec, Vec); + ) -> Result<(Vec, Vec), String>; } ``` -Now to be totally fair I have no real understanding of state reso From 954fe5e51e34b7c580a88cb3b8d91b76f7a6128f Mon Sep 17 00:00:00 2001 From: Devin R Date: Sun, 19 Jul 2020 08:42:45 -0400 Subject: [PATCH 005/130] Done transcribing all logic is filled in no more unimplemented! --- Cargo.toml | 2 + src/event_auth.rs | 567 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 238 +++++++++++++++++-- src/room_version.rs | 147 ++++++++++++ src/state_event.rs | 133 +++++++++-- src/state_store.rs | 14 +- tests/init.rs | 8 +- 7 files changed, 1064 insertions(+), 45 deletions(-) create mode 100644 src/event_auth.rs create mode 100644 src/room_version.rs diff --git a/Cargo.toml b/Cargo.toml index e72bb7e4..bb725f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/event_auth.rs b/src/event_auth.rs new file mode 100644 index 00000000..4b0ebe11 --- /dev/null +++ b/src/event_auth.rs @@ -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::() { + 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, +) -> Option { + 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::( + 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) -> 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, +) -> Option { + let content = event + .deserialize_content::() + .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::() + { + 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::() + .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, +) -> Option { + let mem = auth_events.get(&(EventType::RoomMember, event.sender().to_string()))?; + // TODO this is check_membership + Some( + mem.deserialize_content::() + .ok()? + .membership + == MembershipState::Join, + ) +} + +/// Is the user allowed to send a specific event. +fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { + 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, +) -> Option { + 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::() + .ok()?; + let current_content = current_state + .deserialize_content::() + .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, +) -> Option { + 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::(event.content()) + { + content.membership == state + } else { + false + } + } else { + false + } +} + +fn get_named_level(auth_events: &StateMap, 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) -> i64 { + if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { + if let Ok(content) = pl.deserialize_content::() + { + 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::() { + if &c.creator == user_id { + 100 + } else { + 0 + } + } else { + 0 + } + } else { + 0 + } + } +} + +fn get_send_level( + e_type: EventType, + state_key: Option, + power_lvl: Option<&StateEvent>, +) -> i64 { + if let Some(ple) = power_lvl { + if state_key.is_some() { + if let Ok(content) = + serde_json::from_value::(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; + } +} diff --git a/src/lib.rs b/src/lib.rs index 49e31546..13008103 100644 --- a/src/lib.rs +++ b/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::>(), ) .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::>(); // 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], - event_map: &EventMap, + _event_map: &EventMap, store: &dyn StateStore, ) -> Result, 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::>(), + ) } - fn revers_topological_power_sort( + fn reverse_topological_power_sort( &mut self, room_id: &RoomId, power_events: &[EventId], event_map: &EventMap, store: &dyn StateStore, - conflicted_set: &[EventId], + auth_diff: &[EventId], ) -> Vec { 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( + &mut self, + graph: &BTreeMap>, + key_fn: F, + ) -> Vec + where + F: Fn(&EventId) -> (i64, SystemTime, Option), + { + // 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, + 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::() + { + if &content.creator == aev.sender() { + return 100; + } + break; + } + } + } + } + return 0; + } + + if let Some(content) = pl + .map(|pl| { + pl.deserialize_content::( + ) + .ok() + }) + .flatten() + { + content.users_default.into() + } else { + 0 + } } fn iterative_auth_check( @@ -224,9 +365,41 @@ impl StateResolution { unconflicted_state: &StateMap, event_map: &EventMap, store: &dyn StateStore, - ) -> StateMap { + ) -> Result, 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>, + 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 { diff --git a/src/room_version.rs b/src/room_version.rs new file mode 100644 index 00000000..4c527713 --- /dev/null +++ b/src/room_version.rs @@ -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, + } + } +} diff --git a/src/state_event.rs b/src/state_event.rs index 3fe07b4b..d8df36ce 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -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( + &self, + ) -> Result { + 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 { + 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 { + 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::::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 { diff --git a/src/state_store.rs b/src/state_store.rs index d5c7c862..37c357e0 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -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, String>; + /// Returns a Vec representing the difference in auth chains of the given `events`. + fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result, 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( diff --git a/tests/init.rs b/tests/init.rs index a3c4936b..f0aa8839 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -155,6 +155,10 @@ impl StateStore for TestStore { ]) } + fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result, 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(); From 40248ef40bbc90ddf139acb0653b6ee6b025685a Mon Sep 17 00:00:00 2001 From: Devin R Date: Sun, 19 Jul 2020 09:22:07 -0400 Subject: [PATCH 006/130] Reviewed to reverse_topo_power_sort --- src/lib.rs | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 13008103..de63dfa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ impl StateResolution { let mut event_map = EventMap::new(); // split non-conflicting and conflicting state - let (clean, conflicting) = self.seperate(&state_sets); + let (clean, conflicting) = self.separate(&state_sets); if conflicting.is_empty() { return Ok(ResolutionResult::Resolved(clean)); @@ -115,7 +115,8 @@ impl StateResolution { } } - // 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?? + // synapse says `full_set = {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) @@ -180,7 +181,7 @@ impl StateResolution { /// Split the events that have no conflicts from those that are conflicting. /// /// The tuple looks like `(unconflicted, conflicted)`. - fn seperate( + fn separate( &mut self, state_sets: &[StateMap], ) -> (StateMap, StateMap>) { @@ -259,13 +260,18 @@ impl StateResolution { } self.lexicographical_topological_sort(&mut graph, |event_id| { - let ev = event_map.get(event_id).cloned().unwrap(); + let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - + // This return value is the key used for sorting events, + // events are then sorted by power level, time, + // and lexically by event_id. (*pl, ev.origin_server_ts().clone(), ev.event_id().cloned()) }) } + /// Sorts the event graph based on number of outgoing/incoming edges, where + /// `key_fn` is used as a tie breaker. The tie breaker happens based on + /// power level, age, and event_id. fn lexicographical_topological_sort( &mut self, graph: &BTreeMap>, @@ -274,13 +280,16 @@ impl StateResolution { where F: Fn(&EventId) -> (i64, SystemTime, Option), { - // Note, this is basically Kahn's algorithm except we look at nodes with no + // NOTE: an event that has no incoming edges happened most recently, + // and an event that has no outgoing edges happened least recently. + + // 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. + // Vec of nodes that have zero out degree, least recent events. let mut zero_outdegree = vec![]; for (node, edges) in graph.iter() { @@ -296,6 +305,8 @@ impl StateResolution { let mut heap = BinaryHeap::from(zero_outdegree); + // we remove the oldest node (most incoming edges) and check against all other + // while let Some((_, node)) = heap.pop() { for parent in reverse_graph.get(node).unwrap() { let out = outdegree_map.get(parent).unwrap(); @@ -305,14 +316,20 @@ impl StateResolution { } } - heap.into_iter().map(|(_, id)| id).cloned().collect() + // rust BinaryHeap does not iter in order so we gotta do it the long way + let mut sorted = vec![]; + while let Some((_, id)) = heap.pop() { + sorted.push(id.clone()) + } + + sorted } fn get_power_level_for_sender( &self, room_id: &RoomId, event_id: &EventId, - event_map: &EventMap, + _event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> i64 { let mut pl = None; @@ -363,7 +380,7 @@ impl StateResolution { room_version: &RoomVersionId, power_events: &[EventId], unconflicted_state: &StateMap, - event_map: &EventMap, + _event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> Result, String> { tracing::debug!("starting iter auth check"); @@ -542,11 +559,6 @@ impl StateResolution { pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool { match store.get_event(event_id) { Ok(state) => state.is_power_event(), - _ => false, // TODO this shouldn't eat errors + _ => false, // TODO this shouldn't eat errors? } } - -#[cfg(test)] -mod tests { - use super::*; -} From 0c21f38cb1d7e32276ed9e554a7d93b3749d430d Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 20 Jul 2020 22:02:29 -0400 Subject: [PATCH 007/130] Fixing failing first failing state res test lexicographical_topological_sort test passes. Chasing bug somewhere in resolve. --- Cargo.toml | 6 +- README.md | 11 +- src/event_auth.rs | 4 +- src/lib.rs | 88 ++++-- src/state_event.rs | 17 +- src/state_store.rs | 23 +- tests/event_auth.rs | 0 tests/init.rs | 726 ++++++++++++++++++++++++++++++++++---------- tests/resolve.rs | 1 + 9 files changed, 655 insertions(+), 221 deletions(-) create mode 100644 tests/event_auth.rs create mode 100644 tests/resolve.rs diff --git a/Cargo.toml b/Cargo.toml index bb725f44..1745e72d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,12 @@ petgraph = "0.5.1" serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.56" tracing = "0.1.16" +maplit = "1.0.2" [dependencies.ruma] -git = "https://github.com/ruma/ruma" +# git = "https://github.com/ruma/ruma" +path = "../__forks__/ruma/ruma" features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] -maplit = "1.0.2" +lazy_static = "1.4.0" diff --git a/README.md b/README.md index 759c0565..d5a37561 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,7 @@ trait StateStore { fn get_events(&self, event_ids: &[EventId]) -> Result, String>; /// Returns a Vec of the related auth events to the given `event`. - fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String>; - - /// Returns a tuple of requested state events from `event_id` and the auth chain events that - /// they relate to the. - fn get_remote_state_for_room( - &self, - room_id: &RoomId, - version: &RoomVersionId, - event_id: &EventId, - ) -> Result<(Vec, Vec), String>; + fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result, String>; } diff --git a/src/event_auth.rs b/src/event_auth.rs index 4b0ebe11..f9c40259 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -20,7 +20,7 @@ pub enum RedactAllowed { No, } -pub(crate) fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> { +pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> { if event.kind() == EventType::RoomCreate { return vec![]; } @@ -50,7 +50,7 @@ pub(crate) fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String auth_types } -pub(crate) fn auth_check( +pub fn auth_check( room_version: &RoomVersionId, event: &StateEvent, auth_events: StateMap, diff --git a/src/lib.rs b/src/lib.rs index de63dfa6..36053264 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,10 @@ use std::{ - collections::{BTreeMap, BinaryHeap}, + cmp::Reverse, + collections::{BTreeMap, BTreeSet, BinaryHeap}, time::SystemTime, }; +use maplit::btreeset; use ruma::{ events::EventType, identifiers::{EventId, RoomId, RoomVersionId}, @@ -14,6 +16,7 @@ mod room_version; mod state_event; mod state_store; +pub use event_auth::{auth_check, auth_types_for_event}; pub use state_event::StateEvent; pub use state_store::StateStore; @@ -75,7 +78,10 @@ impl StateResolution { tracing::debug!("computing {} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks - let mut auth_diff = self.get_auth_chain_diff(&state_sets, &mut event_map, store)?; + let mut auth_diff = + self.get_auth_chain_diff(room_id, &state_sets, &mut event_map, store)?; + + println!("{:?}", auth_diff); // add the auth_diff to conflicting now we have a full set of conflicting events auth_diff.extend(conflicting.values().cloned().flatten()); @@ -181,7 +187,7 @@ impl StateResolution { /// Split the events that have no conflicts from those that are conflicting. /// /// The tuple looks like `(unconflicted, conflicted)`. - fn separate( + pub fn separate( &mut self, state_sets: &[StateMap], ) -> (StateMap, StateMap>) { @@ -206,8 +212,9 @@ impl StateResolution { } /// Returns a Vec of deduped EventIds that appear in some chains but no others. - fn get_auth_chain_diff( + pub fn get_auth_chain_diff( &mut self, + room_id: &RoomId, state_sets: &[StateMap], _event_map: &EventMap, store: &dyn StateStore, @@ -216,6 +223,7 @@ impl StateResolution { tracing::debug!("calculating auth chain difference"); store.auth_chain_diff( + room_id, &state_sets .iter() .flat_map(|map| map.values()) @@ -224,7 +232,7 @@ impl StateResolution { ) } - fn reverse_topological_power_sort( + pub fn reverse_topological_power_sort( &mut self, room_id: &RoomId, power_events: &[EventId], @@ -272,7 +280,7 @@ impl StateResolution { /// Sorts the event graph based on number of outgoing/incoming edges, where /// `key_fn` is used as a tie breaker. The tie breaker happens based on /// power level, age, and event_id. - fn lexicographical_topological_sort( + pub fn lexicographical_topological_sort( &mut self, graph: &BTreeMap>, key_fn: F, @@ -286,7 +294,12 @@ impl StateResolution { // 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; + + // TODO make the BTreeSet conversion cleaner ?? + let mut outdegree_map: BTreeMap> = graph + .into_iter() + .map(|(k, v)| (k.clone(), v.into_iter().cloned().collect())) + .collect(); let mut reverse_graph = BTreeMap::new(); // Vec of nodes that have zero out degree, least recent events. @@ -294,34 +307,46 @@ impl StateResolution { for (node, edges) in graph.iter() { if edges.is_empty() { - zero_outdegree.push((key_fn(node), node)); + // the `Reverse` is because rusts bin heap sorts largest -> smallest we need + // smallest -> largest + zero_outdegree.push(Reverse((key_fn(node), node))); } - reverse_graph.insert(node, vec![]); + reverse_graph.entry(node).or_insert(btreeset![]); for edge in edges { - reverse_graph.entry(edge).or_insert(vec![]).push(node); + reverse_graph + .entry(edge) + .or_insert(btreeset![]) + .insert(node); } } let mut heap = BinaryHeap::from(zero_outdegree); // we remove the oldest node (most incoming edges) and check against all other - // - while let Some((_, node)) = heap.pop() { + let mut sorted = vec![]; + // match out the `Reverse` and take the smallest `node` each time + while let Some(Reverse((_, node))) = heap.pop() { + let node: &EventId = node; 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)); + // the number of outgoing edges this node has + let out = outdegree_map.get_mut(parent).unwrap(); + + // only push on the heap once older events have been cleared + out.remove(node); + if out.is_empty() { + heap.push(Reverse((key_fn(parent), parent))); } } + + // synapse yields we push then return the vec + sorted.push(node.clone()); } - // rust BinaryHeap does not iter in order so we gotta do it the long way - let mut sorted = vec![]; - while let Some((_, id)) = heap.pop() { - sorted.push(id.clone()) - } - + // println!( + // "{:#?}", + // sorted.iter().map(ToString::to_string).collect::>() + // ); sorted } @@ -333,7 +358,7 @@ impl StateResolution { store: &dyn StateStore, ) -> i64 { let mut pl = None; - for aid in store.auth_event_ids(room_id, event_id).unwrap() { + for aid in store.auth_event_ids(room_id, &[event_id.clone()]).unwrap() { if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); @@ -343,7 +368,7 @@ impl StateResolution { } if pl.is_none() { - for aid in store.auth_event_ids(room_id, event_id).unwrap() { + for aid in store.auth_event_ids(room_id, &[event_id.clone()]).unwrap() { if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomCreate, "") { if let Ok(content) = aev @@ -384,12 +409,14 @@ impl StateResolution { store: &dyn StateStore, ) -> Result, String> { tracing::debug!("starting iter auth check"); + 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() { + for aid in store.auth_event_ids(room_id, &[event_id.clone()]).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); @@ -408,7 +435,11 @@ impl StateResolution { } } - if !event_auth::auth_check(room_version, &event, auth_events).ok_or("".to_string())? {} + if !event_auth::auth_check(room_version, &event, auth_events) + .ok_or("Auth check failed due to deserialization most likely".to_string())? + { + // TODO synapse passes here on AuthError ?? + } // We yield occasionally when we're working with large data sets to // ensure that we don't block the reactor loop for too long. @@ -441,7 +472,7 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); // We don't need the actual pl_ev here since we delegate to the store - let auth_events = store.auth_event_ids(room_id, &p).unwrap(); + let auth_events = store.auth_event_ids(room_id, &[p]).unwrap(); pl = None; for aid in auth_events { let ev = store.get_event(&aid).unwrap(); @@ -490,6 +521,7 @@ impl StateResolution { } // sort the event_ids by their depth, timestamp and EventId + // unwrap is OK order map and sort_event_ids are from to_sort (the same Vec) sort_event_ids.sort_by_key(|sort_id| order_map.get(sort_id).unwrap()); sort_event_ids @@ -510,7 +542,7 @@ impl StateResolution { } let auth_events = if let Some(id) = sort_ev.event_id() { - store.auth_event_ids(room_id, id).unwrap() + store.auth_event_ids(room_id, &[id.clone()]).unwrap() } else { vec![] }; @@ -542,7 +574,7 @@ impl StateResolution { let eid = state.pop().unwrap(); graph.insert(eid.clone(), vec![]); - for aid in store.auth_event_ids(room_id, &eid).unwrap() { + for aid in store.auth_event_ids(room_id, &[eid.clone()]).unwrap() { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { state.push(aid.clone()); diff --git a/src/state_event.rs b/src/state_event.rs index d8df36ce..d24f5e3b 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -171,7 +171,7 @@ impl StateEvent { pub fn prev_event_ids(&self) -> Vec { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, Self::Sync(ev) => match ev { @@ -183,6 +183,21 @@ impl StateEvent { } } + pub fn auth_event_ids(&self) -> Vec { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.clone(), + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => { + ev.auth_events.iter().map(|(id, _)| id).cloned().collect() + } + PduStub::RoomV3PduStub(ev) => ev.auth_events.clone(), + }, + } + } + pub fn content(&self) -> serde_json::Value { match self { Self::Full(ev) => match ev { diff --git a/src/state_store.rs b/src/state_store.rs index 37c357e0..757444ab 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,4 +1,4 @@ -use ruma::identifiers::{EventId, RoomId, RoomVersionId}; +use ruma::identifiers::{EventId, RoomId}; use crate::StateEvent; @@ -10,17 +10,16 @@ pub trait StateStore { fn get_events(&self, event_ids: &[EventId]) -> Result, String>; /// Returns a Vec of the related auth events to the given `event`. - fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String>; - - /// Returns a Vec representing the difference in auth chains of the given `events`. - fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result, 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( + fn auth_event_ids( &self, room_id: &RoomId, - version: &RoomVersionId, - event_id: &EventId, - ) -> Result<(Vec, Vec), String>; + event_ids: &[EventId], + ) -> Result, String>; + + /// Returns a Vec representing the difference in auth chains of the given `events`. + fn auth_chain_diff( + &self, + room_id: &RoomId, + event_id: &[&EventId], + ) -> Result, String>; } diff --git a/tests/event_auth.rs b/tests/event_auth.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/init.rs b/tests/init.rs index f0aa8839..7ab1d278 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -1,208 +1,602 @@ -use std::{collections::BTreeMap, convert::TryFrom}; +#![allow(unused)] + +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::TryFrom, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; use maplit::btreemap; use ruma::{ events::{ - room::{self}, - AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType, + pdu::Pdu, + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, }, - identifiers::{EventId, RoomId, RoomVersionId}, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{from_value as from_json_value, json, Value as JsonValue}; -use state_res::{ResolutionResult, StateEvent, StateResolution, StateStore}; +use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; -// TODO make this an array of events -fn federated_json() -> JsonValue { - json!({ - "content": { - "creator": "@example:example.org", - "m.federate": true, - "predecessor": { - "event_id": "$something:example.org", - "room_id": "!oldroom:example.org" - }, - "room_version": "6" - }, - "event_id": "$aaa:example.org", - "origin_server_ts": 1, - "room_id": "!room_id:example.org", - "sender": "@alice:example.org", - "state_key": "", - "type": "m.room.create", - "unsigned": { - "age": 1234 - } - }) +static mut SERVER_TIMESTAMP: i32 = 0; + +fn id(id: &str) -> EventId { + EventId::try_from(format!("${}:foo", id)).unwrap() } -fn room_create() -> JsonValue { - json!({ - "content": { - "creator": "@example:example.org", - "m.federate": true, - "predecessor": { - "event_id": "$something:example.org", - "room_id": "!oldroom:example.org" - }, - "room_version": "6" - }, - "event_id": "$aaa:example.org", - "origin_server_ts": 1, - "room_id": "!room_id:example.org", - "sender": "@alice:example.org", - "state_key": "", - "type": "m.room.create", - "unsigned": { - "age": 1234 - } - }) +fn alice() -> UserId { + UserId::try_from("@alice:example.com").unwrap() +} +fn bobo() -> UserId { + UserId::try_from("@bobo:example.com").unwrap() +} +fn devin() -> UserId { + UserId::try_from("@devin:example.com").unwrap() +} +fn zera() -> UserId { + UserId::try_from("@zera:example.com").unwrap() } -fn join_rules() -> JsonValue { - json!({ - "content": { - "join_rule": "public" - }, - "event_id": "$bbb:example.org", - "origin_server_ts": 2, - "room_id": "!room_id:example.org", - "sender": "@alice:example.org", - "state_key": "", - "type": "m.room.join_rules", - "unsigned": { - "age": 1234 - } - }) +fn room_id() -> RoomId { + RoomId::try_from("!test:example.com").unwrap() } -fn join_event() -> JsonValue { - json!({ - "content": { - "avatar_url": null, - "displayname": "example", - "membership": "join" - }, - "event_id": "$ccc:example.org", - "membership": "join", - "room_id": "!room_id:example.org", - "origin_server_ts": 3, - "sender": "@alice:example.org", - "state_key": "@alice:example.org", - "type": "m.room.member", - "unsigned": { - "age": 1, - "replaces_state": "$151800111315tsynI:example.org", - "prev_content": { - "avatar_url": null, - "displayname": "example", - "membership": "invite" - } - } +fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, }) + .unwrap() +} +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() } -fn power_levels() -> JsonValue { - json!({ - "content": { - "ban": 50, - "events": { - "m.room.name": 100, - "m.room.power_levels": 100 - }, - "events_default": 0, - "invite": 50, - "kick": 50, - "notifications": { - "room": 20 - }, - "redact": 50, - "state_default": 50, - "users": { - "@example:example.org": 100 - }, - "users_default": 0 - }, - "event_id": "$ddd:example.org", - "origin_server_ts": 4, - "room_id": "!room_id:example.org", - "sender": "@example:example.org", - "state_key": "", - "type": "m.room.power_levels", - "unsigned": { - "age": 1234 - } - }) +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[EventId], + prev_events: &[EventId], +) -> StateEvent { + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains("$") { + id.to_string() + } else { + format!("${}:foo", id) + }; + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() } -pub struct TestStore; +fn to_init_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, +) -> StateEvent { + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains("$") { + id.to_string() + } else { + format!("${}:foo", id) + }; + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": [], + "prev_events": [], + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": [], + "prev_events": [], + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + vec![ + to_init_pdu_event( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + ), + to_init_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + ), + to_init_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + ), + to_init_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + ), + to_init_pdu_event( + "IMB", + bobo(), + EventType::RoomMember, + Some(bobo().to_string().as_str()), + member_content_join(), + ), + to_init_pdu_event( + "IMC", + devin(), + EventType::RoomMember, + Some(devin().to_string().as_str()), + member_content_join(), + ), + to_init_pdu_event( + "IMZ", + zera(), + EventType::RoomMember, + Some(zera().to_string().as_str()), + member_content_join(), + ), + to_init_pdu_event("START", zera(), EventType::RoomMessage, None, json!({})), + to_init_pdu_event("END", zera(), EventType::RoomMessage, None, json!({})), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} + +#[allow(non_snake_case)] +fn INITIAL_EDGES() -> Vec { + vec![ + "START", "IMZ", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE", + ] + .into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap() +} + +pub struct TestStore(BTreeMap); + +#[allow(unused)] impl StateStore for TestStore { fn get_events(&self, events: &[EventId]) -> Result, String> { - vec![room_create(), join_rules(), join_event(), power_levels()] - .into_iter() - .map(from_json_value) - .collect::>>() - .map_err(|e| e.to_string()) + Ok(self + .0 + .iter() + .filter(|e| events.contains(e.0)) + .map(|(_, s)| s) + .cloned() + .collect()) } fn get_event(&self, event_id: &EventId) -> Result { - from_json_value(power_levels()).map_err(|e| e.to_string()) + self.0 + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) } - fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result, String> { - Ok(vec![ - EventId::try_from("$aaa:example.org").map_err(|e| e.to_string())? - ]) - } - - fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result, String> { - Ok(vec![]) - } - - fn get_remote_state_for_room( + fn auth_event_ids( &self, room_id: &RoomId, - version: &RoomVersionId, - event_id: &EventId, - ) -> Result<(Vec, Vec), String> { - Ok(( - vec![from_json_value(federated_json()).map_err(|e| e.to_string())?], - vec![from_json_value(power_levels()).map_err(|e| e.to_string())?], - )) + event_ids: &[EventId], + ) -> Result, String> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(&ev_id).unwrap(); + for aid in event.auth_event_ids() { + stack.push(aid); + } + } + + Ok(result) } + + fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: &[&EventId], + ) -> Result, String> { + let mut chains = BTreeSet::new(); + let mut list = vec![]; + for id in event_ids { + let chain = self + .auth_event_ids(room_id, &[(*id).clone()])? + .into_iter() + .collect::>(); + list.push(chain.clone()); + chains.insert(chain); + } + if let Some(chain) = list.first() { + let set = maplit::btreeset!(chain.clone()); + let common = set.intersection(&chains).flatten().collect::>(); + Ok(chains + .iter() + .flatten() + .filter(|id| common.contains(&id)) + .cloned() + .collect()) + } else { + Ok(vec![]) + } + } +} + +fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { + let mut resolver = StateResolution::default(); + // TODO what do we fill this with, everything ?? + let store = TestStore( + INITIAL_EVENTS() + .values() + .chain(events) + .map(|ev| (ev.event_id().unwrap().clone(), ev.clone())) + .collect(), + ); + + // This will be lexi_topo_sorted for resolution + let mut graph = BTreeMap::new(); + // this is the same as in `resolve` event_id -> StateEvent + let mut fake_event_map = BTreeMap::new(); + + // TODO maybe clean up some of these clones it is just tests but... + for ev in INITIAL_EVENTS().values().chain(events) { + graph.insert(ev.event_id().unwrap().clone(), vec![]); + fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); + } + + for edge_list in edges { + for pair in edge_list.windows(2) { + if let &[a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + } + + // event_id -> StateEvent + let mut event_map: BTreeMap = BTreeMap::new(); + // event_id -> StateMap + let mut state_at_event: BTreeMap> = BTreeMap::new(); + + // resolve the current state and add it to the state_at_event map then continue + // on in "time"? + for node in resolver + // TODO is this `key_fn` return correct ?? + .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) + { + let fake_event = fake_event_map.get(&node).unwrap(); + let event_id = fake_event.event_id().unwrap(); + + let prev_events = graph.get(&node).unwrap(); + + let state_before: StateMap = if prev_events.is_empty() { + BTreeMap::new() + } else if prev_events.len() == 1 { + state_at_event.get(&prev_events[0]).unwrap().clone() + } else { + let state_sets = prev_events + .iter() + .filter_map(|k| state_at_event.get(k)) + .cloned() + .collect::>(); + + // println!( + // "resolving {:#?}", + // state_sets + // .iter() + // .map(|map| map + // .iter() + // .map(|((t, s), id)| (t, s, id.to_string())) + // .collect::>()) + // .collect::>() + // ); + + let resolved = + resolver.resolve(&room_id(), &RoomVersionId::version_1(), &state_sets, &store); + match resolved { + Ok(ResolutionResult::Resolved(state)) => state, + _ => panic!("resolution for {} failed", node), + } + }; + + let mut state_after = state_before.clone(); + if fake_event.state_key().is_some() { + let ty = fake_event.kind().clone(); + let key = fake_event.state_key().unwrap().clone(); + state_after.insert((ty, key), event_id.clone()); + } + + let auth_types = state_res::auth_types_for_event(fake_event) + .into_iter() + .collect::>(); + // println!( + // "{:?}", + // auth_types + // .iter() + // .map(|(t, id)| (t, id.to_string())) + // .collect::>() + // ); + + let mut auth_events = vec![]; + for key in auth_types { + if state_before.contains_key(&key) { + auth_events.push(state_before[&key].clone()) + } + } + + // TODO The event is just remade, adding the auth_events and prev_events here + // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better + let e = fake_event; + let event = to_pdu_event( + &e.event_id().unwrap().to_string(), + e.sender().clone(), + e.kind(), + e.state_key().as_deref(), + e.content(), + &auth_events, + prev_events, + ); + + state_at_event.insert(node, state_after); + event_map.insert(event_id.clone(), event); + } + + let mut expected_state = BTreeMap::new(); + for node in expected_state_ids { + let ev = event_map.get(&node).expect(&format!( + "{} not found in {:?}", + node.to_string(), + event_map + .keys() + .map(ToString::to_string) + .collect::>(), + )); + + let key = (ev.kind(), ev.state_key().unwrap()); + + expected_state.insert(key, node); + } + + let start_state = state_at_event + .get(&EventId::try_from("$START:foo").unwrap()) + .unwrap(); + + println!("{:?}", start_state); + + let end_state = state_at_event + .get(&EventId::try_from("$END:foo").unwrap()) + .unwrap() + .iter() + .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>(); + + assert_eq!(expected_state, end_state); } #[test] -fn it_works() { - let mut store = TestStore; +fn ban_vs_power_level() { + let events = &[ + to_init_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 50}}), + ), + to_init_pdu_event( + "MA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + ), + to_init_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(bobo().to_string().as_str()), + member_content_ban(), + ), + to_init_pdu_event( + "PB", + bobo(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 50}}), + ), + ]; - let room_id = RoomId::try_from("!room_id:example.org").unwrap(); - let room_version = RoomVersionId::version_6(); + let edges = vec![ + vec!["END", "MB", "MA", "PA", "START"], + vec!["END", "PB", "PA"], + ] + .into_iter() + .map(|list| { + list.into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap() + }) + .collect::>(); - let initial_state = btreemap! { - (EventType::RoomCreate, "".into()) => EventId::try_from("$aaa:example.org").unwrap(), - }; + let expected_state_ids = vec!["PA", "MA", "MB"] + .into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap(); - let state_to_resolve = btreemap! { - (EventType::RoomCreate, "".into()) => EventId::try_from("$bbb:example.org").unwrap(), - }; + do_check(events, edges, expected_state_ids) +} +// #[test] +fn topic_reset() { + let events = &[ + to_init_pdu_event("T1", alice(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 50}}), + ), + to_init_pdu_event("T2", bobo(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(bobo().to_string().as_str()), + member_content_ban(), + ), + ]; + + let edges = vec![ + vec!["END", "MB", "T2", "PA", "T1", "START"], + vec!["END", "T1"], + ] + .into_iter() + .map(|list| { + list.into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap() + }) + .collect::>(); + + let expected_state_ids = vec!["T1", "MB", "PA"] + .into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap(); + + do_check(events, edges, expected_state_ids) +} + +#[test] +fn test_lexicographical_sort() { let mut resolver = StateResolution::default(); - let res = resolver - .resolve(&room_id, &room_version, &[initial_state], &mut store) - .unwrap(); - assert!(if let ResolutionResult::Resolved(_) = res { - true - } else { - false - }); + let graph = btreemap! { + id("l") => vec![id("o")], + id("m") => vec![id("n"), id("o")], + id("n") => vec![id("o")], + id("o") => vec![], // "o" has zero outgoing edges but 4 incoming edges + id("p") => vec![id("o")], + }; - let resolved = resolver - .resolve(&room_id, &room_version, &[state_to_resolve], &mut store) - .unwrap(); + let res = + resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))); - assert!(resolver.conflicting_events.is_empty()); - assert_eq!(resolver.resolved_events.len(), 3); - assert_eq!(resolver.resolved_events.len(), 3); + assert_eq!( + vec!["o", "l", "n", "m", "p"], + res.iter() + .map(ToString::to_string) + .map(|s| s.replace("$", "").replace(":foo", "")) + .collect::>() + ) } diff --git a/tests/resolve.rs b/tests/resolve.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/resolve.rs @@ -0,0 +1 @@ + From d69e712dba6c7fa7c5b54a1b876e8b258edb772d Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 21 Jul 2020 00:11:03 -0400 Subject: [PATCH 008/130] Working at get_auth_chain_diff --- src/lib.rs | 25 +++++++++++++-- src/state_store.rs | 2 +- tests/init.rs | 80 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 36053264..a029c2c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,13 @@ impl StateResolution { let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, &mut event_map, store)?; - println!("{:?}", auth_diff); + println!( + "AUTH DIFF {:?}", + auth_diff + .iter() + .map(ToString::to_string) + .collect::>() + ); // add the auth_diff to conflicting now we have a full set of conflicting events auth_diff.extend(conflicting.values().cloned().flatten()); @@ -191,6 +197,8 @@ impl StateResolution { &mut self, state_sets: &[StateMap], ) -> (StateMap, StateMap>) { + use itertools::Itertools; + let mut unconflicted_state = StateMap::new(); let mut conflicted_state = StateMap::new(); @@ -198,6 +206,7 @@ impl StateResolution { let mut event_ids = state_sets .iter() .flat_map(|map| map.get(key).cloned()) + .dedup() .collect::>(); if event_ids.len() == 1 { @@ -221,12 +230,22 @@ impl StateResolution { ) -> Result, String> { use itertools::Itertools; + println!( + "{:?}", + state_sets + .iter() + .flat_map(|map| map.values()) + .map(ToString::to_string) + .dedup() + .collect::>() + ); + tracing::debug!("calculating auth chain difference"); store.auth_chain_diff( room_id, - &state_sets + state_sets .iter() - .flat_map(|map| map.values()) + .map(|map| map.values().cloned().collect()) .dedup() .collect::>(), ) diff --git a/src/state_store.rs b/src/state_store.rs index 757444ab..7881883a 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -20,6 +20,6 @@ pub trait StateStore { fn auth_chain_diff( &self, room_id: &RoomId, - event_id: &[&EventId], + event_id: Vec>, ) -> Result, String>; } diff --git a/tests/init.rs b/tests/init.rs index 7ab1d278..6fc0c57c 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -1,6 +1,7 @@ #![allow(unused)] use std::{ + cell::RefCell, collections::{BTreeMap, BTreeSet}, convert::TryFrom, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -28,20 +29,20 @@ fn id(id: &str) -> EventId { } fn alice() -> UserId { - UserId::try_from("@alice:example.com").unwrap() + UserId::try_from("@alice:foo").unwrap() } fn bobo() -> UserId { - UserId::try_from("@bobo:example.com").unwrap() + UserId::try_from("@bobo:foo").unwrap() } fn devin() -> UserId { - UserId::try_from("@devin:example.com").unwrap() + UserId::try_from("@devin:foo").unwrap() } fn zera() -> UserId { - UserId::try_from("@zera:example.com").unwrap() + UserId::try_from("@zera:foo").unwrap() } fn room_id() -> RoomId { - RoomId::try_from("!test:example.com").unwrap() + RoomId::try_from("!test:foo").unwrap() } fn member_content_ban() -> JsonValue { @@ -297,26 +298,41 @@ impl StateStore for TestStore { fn auth_chain_diff( &self, room_id: &RoomId, - event_ids: &[&EventId], + event_ids: Vec>, ) -> Result, String> { - let mut chains = BTreeSet::new(); - let mut list = vec![]; - for id in event_ids { + use itertools::Itertools; + + println!( + "EVENTS FOR AUTH {:?}", + event_ids + .iter() + .map(|v| v.iter().map(ToString::to_string).collect::>()) + .collect::>() + ); + + let mut chains = vec![]; + for ids in event_ids { let chain = self - .auth_event_ids(room_id, &[(*id).clone()])? + .auth_event_ids(room_id, &ids)? .into_iter() .collect::>(); - list.push(chain.clone()); - chains.insert(chain); + chains.push(chain); } - if let Some(chain) = list.first() { - let set = maplit::btreeset!(chain.clone()); - let common = set.intersection(&chains).flatten().collect::>(); + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + println!( + "COMMON {:?}", + common.iter().map(ToString::to_string).collect::>() + ); Ok(chains .iter() .flatten() - .filter(|id| common.contains(&id)) + .filter(|id| !common.contains(&id)) .cloned() + .collect::>() + .into_iter() .collect()) } else { Ok(vec![]) @@ -325,6 +341,8 @@ impl StateStore for TestStore { } fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { + use itertools::Itertools; + let mut resolver = StateResolution::default(); // TODO what do we fill this with, everything ?? let store = TestStore( @@ -340,12 +358,19 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // this is the same as in `resolve` event_id -> StateEvent let mut fake_event_map = BTreeMap::new(); + // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { graph.insert(ev.event_id().unwrap().clone(), vec![]); fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); } + for pair in INITIAL_EDGES().windows(2) { + if let &[a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + for edge_list in edges { for pair in edge_list.windows(2) { if let &[a, b] = &pair { @@ -392,8 +417,12 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // .collect::>() // ); - let resolved = - resolver.resolve(&room_id(), &RoomVersionId::version_1(), &state_sets, &store); + let resolved = resolver.resolve( + &room_id(), + &RoomVersionId::version_1(), + &state_sets, + &TestStore(event_map.clone()), + ); match resolved { Ok(ResolutionResult::Resolved(state)) => state, _ => panic!("resolution for {} failed", node), @@ -428,6 +457,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO The event is just remade, adding the auth_events and prev_events here // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; + let ev_id = e.event_id().unwrap(); let event = to_pdu_event( &e.event_id().unwrap().to_string(), e.sender().clone(), @@ -437,6 +467,9 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: &auth_events, prev_events, ); + // we have to update our store, an actual user of this lib would do this + // with the result of the resolution> + // *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); event_map.insert(event_id.clone(), event); @@ -462,13 +495,18 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .get(&EventId::try_from("$START:foo").unwrap()) .unwrap(); - println!("{:?}", start_state); - let end_state = state_at_event .get(&EventId::try_from("$END:foo").unwrap()) .unwrap() .iter() - .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) + .filter(|(k, v)| { + println!( + "{:?} == {:?}", + start_state.get(k).map(ToString::to_string), + Some(v.to_string()) + ); + expected_state.contains_key(k) || start_state.get(k) != Some(*v) + }) .map(|(k, v)| (k.clone(), v.clone())) .collect::>(); From 2f443cf41a5f78a5bd8f0e6974d64fbd86448bb0 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 21 Jul 2020 08:55:51 -0400 Subject: [PATCH 009/130] BUG: follows synapse until get_mainline_depth then it loops forever --- src/event_auth.rs | 18 +++++++--- src/lib.rs | 86 ++++++++++++++++++++++++++++++++++++++++------- tests/init.rs | 31 ++++++++--------- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index f9c40259..dffc8065 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -34,19 +34,29 @@ pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> { if event.kind() == EventType::RoomMember { if let Ok(content) = event.deserialize_content::() { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { - auth_types.push((EventType::RoomJoinRules, "".into())) + let key = (EventType::RoomJoinRules, "".into()); + if !auth_types.contains(&key) { + auth_types.push(key) + } } - // TODO is None the same as "" for state_key, probably NOT - auth_types.push((EventType::RoomMember, event.state_key().unwrap_or_default())); + // TODO what when we don't find a state_key + let key = (EventType::RoomMember, event.state_key().unwrap()); + if !auth_types.contains(&key) { + auth_types.push(key) + } if content.membership == MembershipState::Invite { if let Some(t_id) = content.third_party_invite { - auth_types.push((EventType::RoomThirdPartyInvite, t_id.signed.token)) + let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); + if !auth_types.contains(&key) { + auth_types.push(key) + } } } } } + auth_types } diff --git a/src/lib.rs b/src/lib.rs index a029c2c1..6e241900 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,12 +62,18 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], + // TODO remove or make this mut so we aren't cloning the whole thing + event_map: Option>, store: &dyn StateStore, // TODO actual error handling (`thiserror`??) ) -> Result { tracing::debug!("State resolution starting"); - let mut event_map = EventMap::new(); + let mut event_map = if let Some(ev_map) = event_map { + ev_map + } else { + EventMap::new() + }; // split non-conflicting and conflicting state let (clean, conflicting) = self.separate(&state_sets); @@ -78,21 +84,24 @@ impl StateResolution { tracing::debug!("computing {} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks - let mut auth_diff = - self.get_auth_chain_diff(room_id, &state_sets, &mut event_map, store)?; + let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, &event_map, store)?; + + // add the auth_diff to conflicting now we have a full set of conflicting events + auth_diff.extend(conflicting.values().cloned().flatten()); + let mut all_conflicted = auth_diff + .into_iter() + .collect::>() + .into_iter() + .collect::>(); println!( - "AUTH DIFF {:?}", - auth_diff + "FULL CONF {:?}", + all_conflicted .iter() .map(ToString::to_string) .collect::>() ); - // add the auth_diff to conflicting now we have a full set of conflicting events - auth_diff.extend(conflicting.values().cloned().flatten()); - let mut all_conflicted = auth_diff; - tracing::debug!("full conflicted set is {} events", all_conflicted.len()); // gather missing events for the event_map @@ -106,6 +115,7 @@ impl StateResolution { .collect::>(), ) .unwrap(); + // update event_map to include the fetched events event_map.extend( events @@ -137,6 +147,13 @@ impl StateResolution { .filter(|id| is_power_event(id, store)) .cloned() .collect::>(); + println!( + "POWER {:?}", + power_events + .iter() + .map(ToString::to_string) + .collect::>() + ); // sort the power events based on power_level/clock/event_id and outgoing/incoming edges let mut sorted_power_levels = self.reverse_topological_power_sort( @@ -147,6 +164,14 @@ impl StateResolution { &all_conflicted, ); + println!( + "SRTD {:?}", + sorted_power_levels + .iter() + .map(ToString::to_string) + .collect::>() + ); + // sequentially auth check each event. let resolved = self.iterative_auth_check( room_id, @@ -157,6 +182,8 @@ impl StateResolution { store, )?; + println!("AUTHED {:?}", resolved.iter().collect::>()); + // 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(); @@ -165,15 +192,33 @@ impl StateResolution { // 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)) + .filter(|id| !deduped_power_ev.contains(id)) .cloned() .collect::>(); + println!( + "LEFT {:?}", + events_to_resolve + .iter() + .map(ToString::to_string) + .collect::>() + ); + let power_event = resolved.get(&(EventType::RoomPowerLevels, "".into())); + println!("PL {:?}", power_event); + let sorted_left_events = self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); + println!( + "SORTED LEFT {:?}", + events_to_resolve + .iter() + .map(ToString::to_string) + .collect::>() + ); + let mut resolved_state = self.iterative_auth_check( room_id, room_version, @@ -276,6 +321,7 @@ impl StateResolution { 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); + println!("{}", pl); event_to_pl.insert(event_id.clone(), pl); @@ -287,6 +333,7 @@ impl StateResolution { } self.lexicographical_topological_sort(&mut graph, |event_id| { + println!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); // This return value is the key used for sorting events, @@ -510,6 +557,7 @@ impl StateResolution { let mainline_map = mainline .iter() + .rev() .enumerate() .map(|(idx, eid)| ((*eid).clone(), idx)) .collect::>(); @@ -553,6 +601,7 @@ impl StateResolution { mainline_map: &EventMap, store: &dyn StateStore, ) -> usize { + let mut count = 0; while let Some(sort_ev) = event { if let Some(id) = sort_ev.event_id() { if let Some(depth) = mainline_map.get(id) { @@ -565,6 +614,18 @@ impl StateResolution { } else { vec![] }; + if count < 15 { + println!( + "{:?}", + auth_events + .iter() + .map(ToString::to_string) + .collect::>() + ); + } else { + panic!("{}", sort_ev.event_id().unwrap().to_string()) + } + count += 1; event = None; for aid in auth_events { @@ -591,8 +652,9 @@ impl StateResolution { 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![]); - + graph.entry(eid.clone()).or_insert(vec![]); + // prefer the store to event as the store filters dedups the events + // otherwise it seems we can loop forever for aid in store.auth_event_ids(room_id, &[eid.clone()]).unwrap() { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { diff --git a/tests/init.rs b/tests/init.rs index 6fc0c57c..07dd77d8 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -249,13 +249,14 @@ fn INITIAL_EDGES() -> Vec { .unwrap() } -pub struct TestStore(BTreeMap); +pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { fn get_events(&self, events: &[EventId]) -> Result, String> { Ok(self .0 + .borrow() .iter() .filter(|e| events.contains(e.0)) .map(|(_, s)| s) @@ -265,6 +266,7 @@ impl StateStore for TestStore { fn get_event(&self, event_id: &EventId) -> Result { self.0 + .borrow() .get(event_id) .cloned() .ok_or(format!("{} not found", event_id.to_string())) @@ -345,13 +347,13 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: let mut resolver = StateResolution::default(); // TODO what do we fill this with, everything ?? - let store = TestStore( + let store = TestStore(RefCell::new( INITIAL_EVENTS() .values() .chain(events) .map(|ev| (ev.event_id().unwrap().clone(), ev.clone())) .collect(), - ); + )); // This will be lexi_topo_sorted for resolution let mut graph = BTreeMap::new(); @@ -361,6 +363,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { + println!("{:?}", ev.event_id().unwrap().to_string()); graph.insert(ev.event_id().unwrap().clone(), vec![]); fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); } @@ -390,6 +393,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO is this `key_fn` return correct ?? .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) { + println!("{}", node.to_string()); let fake_event = fake_event_map.get(&node).unwrap(); let event_id = fake_event.event_id().unwrap(); @@ -421,7 +425,8 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: &room_id(), &RoomVersionId::version_1(), &state_sets, - &TestStore(event_map.clone()), + Some(event_map.clone()), + &store, ); match resolved { Ok(ResolutionResult::Resolved(state)) => state, @@ -432,20 +437,16 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: let mut state_after = state_before.clone(); if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); + // we know there is a state_key unwrap OK let key = fake_event.state_key().unwrap().clone(); state_after.insert((ty, key), event_id.clone()); } - let auth_types = state_res::auth_types_for_event(fake_event) - .into_iter() - .collect::>(); - // println!( - // "{:?}", - // auth_types - // .iter() - // .map(|(t, id)| (t, id.to_string())) - // .collect::>() - // ); + let auth_types = state_res::auth_types_for_event(fake_event); + println!( + "AUTH TYPES {:?}", + auth_types.iter().map(|(t, id)| (t, id)).collect::>() + ); let mut auth_events = vec![]; for key in auth_types { @@ -469,7 +470,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: ); // we have to update our store, an actual user of this lib would do this // with the result of the resolution> - // *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); + *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); event_map.insert(event_id.clone(), event); From 5842ddf36e984461549e9db6036f49e358e4bf55 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 22 Jul 2020 23:26:55 -0400 Subject: [PATCH 010/130] Working ban_vs_power_level test, add travis.yml, logging --- .travis.yml | 7 + Cargo.toml | 6 +- src/event_auth.rs | 56 +++++-- src/lib.rs | 121 +++++++------- tests/resolve.rs | 1 - tests/{init.rs => state_res.rs} | 270 +++++++++++++++++++------------- 6 files changed, 272 insertions(+), 189 deletions(-) create mode 100644 .travis.yml delete mode 100644 tests/resolve.rs rename tests/{init.rs => state_res.rs} (87%) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..8f14fa53 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: rust +sudo: false +rust: + - stable + - nightly +script: + - cargo test --all diff --git a/Cargo.toml b/Cargo.toml index 1745e72d..cb14f3cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,12 @@ serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.56" tracing = "0.1.16" maplit = "1.0.2" +tracing-subscriber = "0.2.8" [dependencies.ruma] -# git = "https://github.com/ruma/ruma" -path = "../__forks__/ruma/ruma" +git = "https://github.com/DevinR528/ruma" +branch = "pdu-deserialize" +# path = "../__forks__/ruma/ruma" features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] diff --git a/src/event_auth.rs b/src/event_auth.rs index dffc8065..ecb1f29a 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -65,22 +65,26 @@ pub fn auth_check( event: &StateEvent, auth_events: StateMap, ) -> Option { - tracing::debug!("auth_check begingin"); + tracing::info!("auth_check beginning"); + + // don't let power from other rooms be used 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` + // TODO do_sig_check, do_size_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 { + tracing::info!("start m.room.create check"); + // 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); + return Some(false); // creation events room id does not match senders } // if content.room_version is present and is not a valid version @@ -97,7 +101,7 @@ pub fn auth_check( return Some(false); } - tracing::debug!("m.room.create event was allowed"); + tracing::info!("m.room.create event was allowed"); return Some(true); } @@ -106,18 +110,25 @@ pub fn auth_check( .get(&(EventType::RoomCreate, "".into())) .is_none() { + tracing::warn!("no m.room.create event in auth chain"); + return Some(false); } // check for m.federate if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { + tracing::info!("checking federation"); + if !can_federate(&auth_events) { + tracing::warn!("federation not allowed"); + return Some(false); } } // 4. if type is m.room.aliases if event.kind() == EventType::RoomAliases { + tracing::info!("starting m.room.aliases check"); // TODO && room_version "special case aliases auth" ?? if event.state_key().is_none() { return Some(false); // must have state_key @@ -130,18 +141,29 @@ pub fn auth_check( return Some(false); } - tracing::debug!("m.room.aliases event was allowed"); + tracing::info!("m.room.aliases event was allowed"); return Some(true); } if event.kind() == EventType::RoomMember { + tracing::info!("starting m.room.member check"); + if is_membership_change_allowed(event, &auth_events)? { - tracing::debug!("m.room.member event was allowed"); + tracing::info!("m.room.member membership change was allowed"); return Some(true); } + + tracing::info!("m.room.member event was allowed"); + return Some(true); } - if !check_event_sender_in_room(event, &auth_events)? { + if let Some(in_room) = check_event_sender_in_room(event, &auth_events) { + if !in_room { + tracing::warn!("sender not in room"); + return Some(false); + } + } else { + tracing::warn!("sender not in room"); return Some(false); } @@ -153,6 +175,7 @@ pub fn auth_check( } if !can_send_event(event, &auth_events)? { + tracing::warn!("user cannot send event"); return Some(false); } @@ -160,6 +183,7 @@ pub fn auth_check( if !check_power_levels(room_version, event, &auth_events)? { return Some(false); } + tracing::info!("power levels event allowed"); } if event.kind() == EventType::RoomRedaction { @@ -168,7 +192,7 @@ pub fn auth_check( } } - tracing::debug!("allowing event passed all checks"); + tracing::info!("allowing event passed all checks"); Some(true) } @@ -194,7 +218,8 @@ fn is_membership_change_allowed( ) -> Option { let content = event .deserialize_content::() - .ok()?; + .ok() + .unwrap(); let membership = content.membership; // check if this is the room creator joining @@ -210,17 +235,14 @@ fn is_membership_change_allowed( } } - let target_user_id = UserId::try_from(event.state_key()?).ok()?; + let target_user_id = UserId::try_from(event.state_key().unwrap()).ok().unwrap(); // if the server_names are different and federation is NOT allowed - if event.room_id()?.server_name() != target_user_id.server_name() { + if event.room_id().unwrap().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); @@ -246,7 +268,7 @@ fn is_membership_change_allowed( 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 ##" + // synapse has a not "what to do for default here 50" let ban_level = get_named_level(auth_events, "ban", 50); // TODO clean this up @@ -327,12 +349,14 @@ fn is_membership_change_allowed( } /// Is the event's sender in the room that they sent the event to. +/// +/// A return value of None is not a failure fn check_event_sender_in_room( event: &StateEvent, auth_events: &StateMap, ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, event.sender().to_string()))?; - // TODO this is check_membership + // TODO this is check_membership a helper fn in synapse but it does this Some( mem.deserialize_content::() .ok()? diff --git a/src/lib.rs b/src/lib.rs index 6e241900..d0f18cc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ impl StateResolution { store: &dyn StateStore, // TODO actual error handling (`thiserror`??) ) -> Result { - tracing::debug!("State resolution starting"); + tracing::info!("State resolution starting"); let mut event_map = if let Some(ev_map) = event_map { ev_map @@ -81,7 +81,7 @@ impl StateResolution { return Ok(ResolutionResult::Resolved(clean)); } - tracing::debug!("computing {} conflicting events", conflicting.len()); + tracing::info!("computing {} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, &event_map, store)?; @@ -94,7 +94,7 @@ impl StateResolution { .into_iter() .collect::>(); - println!( + tracing::debug!( "FULL CONF {:?}", all_conflicted .iter() @@ -102,7 +102,7 @@ impl StateResolution { .collect::>() ); - tracing::debug!("full conflicted set is {} events", all_conflicted.len()); + tracing::info!("full conflicted set is {} events", all_conflicted.len()); // gather missing events for the event_map let events = store @@ -147,7 +147,8 @@ impl StateResolution { .filter(|id| is_power_event(id, store)) .cloned() .collect::>(); - println!( + + tracing::debug!( "POWER {:?}", power_events .iter() @@ -164,7 +165,7 @@ impl StateResolution { &all_conflicted, ); - println!( + tracing::debug!( "SRTD {:?}", sorted_power_levels .iter() @@ -172,7 +173,7 @@ impl StateResolution { .collect::>() ); - // sequentially auth check each event. + // sequentially auth check each power level event event. let resolved = self.iterative_auth_check( room_id, room_version, @@ -182,7 +183,13 @@ impl StateResolution { store, )?; - println!("AUTHED {:?}", resolved.iter().collect::>()); + tracing::debug!( + "AUTHED {:?}", + resolved + .iter() + .map(|(key, id)| (key, id.to_string())) + .collect::>() + ); // 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. @@ -196,7 +203,7 @@ impl StateResolution { .cloned() .collect::>(); - println!( + tracing::debug!( "LEFT {:?}", events_to_resolve .iter() @@ -206,14 +213,14 @@ impl StateResolution { let power_event = resolved.get(&(EventType::RoomPowerLevels, "".into())); - println!("PL {:?}", power_event); + tracing::debug!("PL {:?}", power_event); let sorted_left_events = self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); - println!( + tracing::debug!( "SORTED LEFT {:?}", - events_to_resolve + sorted_left_events .iter() .map(ToString::to_string) .collect::>() @@ -244,6 +251,11 @@ impl StateResolution { ) -> (StateMap, StateMap>) { use itertools::Itertools; + tracing::info!( + "seperating {} sets of events into conflicted/unconflicted", + state_sets.len() + ); + let mut unconflicted_state = StateMap::new(); let mut conflicted_state = StateMap::new(); @@ -275,17 +287,8 @@ impl StateResolution { ) -> Result, String> { use itertools::Itertools; - println!( - "{:?}", - state_sets - .iter() - .flat_map(|map| map.values()) - .map(ToString::to_string) - .dedup() - .collect::>() - ); - tracing::debug!("calculating auth chain difference"); + store.auth_chain_diff( room_id, state_sets @@ -321,7 +324,7 @@ impl StateResolution { 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); - println!("{}", pl); + tracing::debug!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -333,7 +336,7 @@ impl StateResolution { } self.lexicographical_topological_sort(&mut graph, |event_id| { - println!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); + // tracing::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); // This return value is the key used for sorting events, @@ -354,6 +357,7 @@ impl StateResolution { where F: Fn(&EventId) -> (i64, SystemTime, Option), { + tracing::info!("starting lexicographical topological sort"); // NOTE: an event that has no incoming edges happened most recently, // and an event that has no outgoing edges happened least recently. @@ -409,7 +413,7 @@ impl StateResolution { sorted.push(node.clone()); } - // println!( + // tracing::debug!( // "{:#?}", // sorted.iter().map(ToString::to_string).collect::>() // ); @@ -423,7 +427,11 @@ impl StateResolution { _event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> i64 { + tracing::info!("fetch event senders ({}) power level", event_id.to_string()); + let mut pl = None; + // TODO store.auth_event_ids returns "self" with the event ids is this ok + // event.auth_event_ids does not include its own event id ? for aid in store.auth_event_ids(room_id, &[event_id.clone()]).unwrap() { if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { @@ -467,25 +475,26 @@ impl StateResolution { fn iterative_auth_check( &mut self, - room_id: &RoomId, + _room_id: &RoomId, room_version: &RoomVersionId, power_events: &[EventId], unconflicted_state: &StateMap, _event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> Result, String> { - tracing::debug!("starting iter auth check"); + tracing::info!("starting iterative auth check"); - let resolved_state = unconflicted_state.clone(); + let mut 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.clone()]).unwrap() { + for aid in event.auth_event_ids() { 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); + // TODO what to do when no state_key is found ?? + // TODO check "rejected_reason", I'm guessing this is redacted_because for ruma ?? + auth_events.insert((ev.kind(), ev.state_key().unwrap()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); } @@ -502,9 +511,14 @@ impl StateResolution { } if !event_auth::auth_check(room_version, &event, auth_events) - .ok_or("Auth check failed due to deserialization most likely".to_string())? + .ok_or("Auth check failed due to deserialization most likely".to_string()) + .unwrap() { // TODO synapse passes here on AuthError ?? + tracing::warn!("event {} failed the authentication", event_id.to_string()); + } else { + // add event to resolved state map + resolved_state.insert((event.kind(), event.state_key().unwrap()), event_id.clone()); } // We yield occasionally when we're working with large data sets to @@ -527,6 +541,10 @@ impl StateResolution { store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of remaining events"); + // tracing::debug!( + // "{:?}", + // to_sort.iter().map(ToString::to_string).collect::>() + // ); // There can be no EventId's to sort, bail. if to_sort.is_empty() { return vec![]; @@ -565,12 +583,7 @@ impl StateResolution { let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - let depth = self.get_mainline_depth( - room_id, - event_map.get(ev_id).cloned(), - &mainline_map, - store, - ); + let depth = self.get_mainline_depth(store.get_event(ev_id).ok(), &mainline_map, store); order_map.insert( ev_id, ( @@ -596,36 +609,30 @@ impl StateResolution { fn get_mainline_depth( &mut self, - room_id: &RoomId, mut event: Option, mainline_map: &EventMap, store: &dyn StateStore, ) -> usize { - let mut count = 0; while let Some(sort_ev) = event { + tracing::debug!( + "mainline EVENT ID {}", + sort_ev.event_id().unwrap().to_string() + ); if let Some(id) = sort_ev.event_id() { if let Some(depth) = mainline_map.get(id) { return *depth; } } - let auth_events = if let Some(id) = sort_ev.event_id() { - store.auth_event_ids(room_id, &[id.clone()]).unwrap() - } else { - vec![] - }; - if count < 15 { - println!( - "{:?}", - auth_events - .iter() - .map(ToString::to_string) - .collect::>() - ); - } else { - panic!("{}", sort_ev.event_id().unwrap().to_string()) - } - count += 1; + let auth_events = sort_ev.auth_event_ids(); + tracing::debug!( + "mainline AUTH EV {:?}", + auth_events + .iter() + .map(ToString::to_string) + .collect::>() + ); + event = None; for aid in auth_events { diff --git a/tests/resolve.rs b/tests/resolve.rs deleted file mode 100644 index 8b137891..00000000 --- a/tests/resolve.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/init.rs b/tests/state_res.rs similarity index 87% rename from tests/init.rs rename to tests/state_res.rs index 07dd77d8..b3b8fb64 100644 --- a/tests/init.rs +++ b/tests/state_res.rs @@ -21,6 +21,7 @@ use ruma::{ }; use serde_json::{from_value as from_json_value, json, Value as JsonValue}; use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; +use tracing_subscriber as tracer; static mut SERVER_TIMESTAMP: i32 = 0; @@ -66,15 +67,18 @@ fn member_content_join() -> JsonValue { .unwrap() } -fn to_pdu_event( +fn to_pdu_event( id: &str, sender: UserId, ev_type: EventType, state_key: Option<&str>, content: JsonValue, - auth_events: &[EventId], - prev_events: &[EventId], -) -> StateEvent { + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ let ts = unsafe { let ts = SERVER_TIMESTAMP; // increment the "origin_server_ts" value @@ -86,6 +90,36 @@ fn to_pdu_event( } else { format!("${}:foo", id) }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(|s| { + EventId::try_from( + if s.contains("$") { + s.to_owned() + } else { + format!("${}:foo", s) + } + .as_str(), + ) + }) + .collect::, _>>() + .unwrap(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(|s| { + EventId::try_from( + if s.contains("$") { + s.to_owned() + } else { + format!("${}:foo", s) + } + .as_str(), + ) + }) + .collect::, _>>() + .unwrap(); let json = if let Some(state_key) = state_key { json!({ @@ -249,102 +283,14 @@ fn INITIAL_EDGES() -> Vec { .unwrap() } -pub struct TestStore(RefCell>); - -#[allow(unused)] -impl StateStore for TestStore { - fn get_events(&self, events: &[EventId]) -> Result, String> { - Ok(self - .0 - .borrow() - .iter() - .filter(|e| events.contains(e.0)) - .map(|(_, s)| s) - .cloned() - .collect()) - } - - fn get_event(&self, event_id: &EventId) -> Result { - self.0 - .borrow() - .get(event_id) - .cloned() - .ok_or(format!("{} not found", event_id.to_string())) - } - - fn auth_event_ids( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { - let mut result = vec![]; - let mut stack = event_ids.to_vec(); - - while !stack.is_empty() { - let ev_id = stack.pop().unwrap(); - if result.contains(&ev_id) { - continue; - } - - result.push(ev_id.clone()); - - let event = self.get_event(&ev_id).unwrap(); - for aid in event.auth_event_ids() { - stack.push(aid); - } - } - - Ok(result) - } - - fn auth_chain_diff( - &self, - room_id: &RoomId, - event_ids: Vec>, - ) -> Result, String> { - use itertools::Itertools; - - println!( - "EVENTS FOR AUTH {:?}", - event_ids - .iter() - .map(|v| v.iter().map(ToString::to_string).collect::>()) - .collect::>() - ); - - let mut chains = vec![]; - for ids in event_ids { - let chain = self - .auth_event_ids(room_id, &ids)? - .into_iter() - .collect::>(); - chains.push(chain); - } - - if let Some(chain) = chains.first() { - let rest = chains.iter().skip(1).flatten().cloned().collect(); - let common = chain.intersection(&rest).collect::>(); - println!( - "COMMON {:?}", - common.iter().map(ToString::to_string).collect::>() - ); - Ok(chains - .iter() - .flatten() - .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() - .collect()) - } else { - Ok(vec![]) - } - } -} - fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { use itertools::Itertools; + // to activate logging use `RUST_LOG=debug cargo t` + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init(); + let mut resolver = StateResolution::default(); // TODO what do we fill this with, everything ?? let store = TestStore(RefCell::new( @@ -363,7 +309,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { - println!("{:?}", ev.event_id().unwrap().to_string()); graph.insert(ev.event_id().unwrap().clone(), vec![]); fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); } @@ -393,7 +338,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO is this `key_fn` return correct ?? .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) { - println!("{}", node.to_string()); let fake_event = fake_event_map.get(&node).unwrap(); let event_id = fake_event.event_id().unwrap(); @@ -411,7 +355,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .collect::>(); // println!( - // "resolving {:#?}", + // "RESOLVING {:?}", // state_sets // .iter() // .map(|map| map @@ -430,7 +374,17 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: ); match resolved { Ok(ResolutionResult::Resolved(state)) => state, - _ => panic!("resolution for {} failed", node), + Ok(ResolutionResult::Conflicted(state)) => panic!( + "conflicted: {:?}", + state + .iter() + .map(|map| map + .iter() + .map(|(key, id)| (key, id.to_string())) + .collect::>()) + .collect::>() + ), + Err(e) => panic!("resolution for {} failed: {}", node, e), } }; @@ -443,10 +397,10 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } let auth_types = state_res::auth_types_for_event(fake_event); - println!( - "AUTH TYPES {:?}", - auth_types.iter().map(|(t, id)| (t, id)).collect::>() - ); + // println!( + // "AUTH TYPES {:?}", + // auth_types.iter().map(|(t, id)| (t, id)).collect::>() + // ); let mut auth_events = vec![]; for key in auth_types { @@ -500,14 +454,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .get(&EventId::try_from("$END:foo").unwrap()) .unwrap() .iter() - .filter(|(k, v)| { - println!( - "{:?} == {:?}", - start_state.get(k).map(ToString::to_string), - Some(v.to_string()) - ); - expected_state.contains_key(k) || start_state.get(k) != Some(*v) - }) + .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) .map(|(k, v)| (k.clone(), v.clone())) .collect::>(); @@ -639,3 +586,100 @@ fn test_lexicographical_sort() { .collect::>() ) } + +// A StateStore implementation for testing +// +// + +/// The test state store. +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_events(&self, events: &[EventId]) -> Result, String> { + Ok(self + .0 + .borrow() + .iter() + .filter(|e| events.contains(e.0)) + .map(|(_, s)| s) + .cloned() + .collect()) + } + + fn get_event(&self, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } + + fn auth_event_ids( + &self, + room_id: &RoomId, + event_ids: &[EventId], + ) -> Result, String> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(&ev_id).unwrap(); + stack.extend(event.auth_event_ids()); + } + + Ok(result) + } + + fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: Vec>, + ) -> Result, String> { + use itertools::Itertools; + + // println!( + // "EVENTS FOR AUTH {:?}", + // event_ids + // .iter() + // .map(|v| v.iter().map(ToString::to_string).collect::>()) + // .collect::>() + // ); + + let mut chains = vec![]; + for ids in event_ids { + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + // println!( + // "COMMON {:?}", + // common.iter().map(ToString::to_string).collect::>() + // ); + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } +} From a0db51b3bd1d6a0553ba549bb97ff0a843394c15 Mon Sep 17 00:00:00 2001 From: Devin R Date: Thu, 23 Jul 2020 01:12:25 -0400 Subject: [PATCH 011/130] Room topic reset test fails --- src/event_auth.rs | 60 +++++++++++++++++++++++++++++++++------------ src/lib.rs | 41 +++++++++++++++++++++---------- src/state_event.rs | 31 +++-------------------- tests/state_res.rs | 61 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 134 insertions(+), 59 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index ecb1f29a..9459d351 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -180,7 +180,14 @@ pub fn auth_check( } if event.kind() == EventType::RoomPowerLevels { - if !check_power_levels(room_version, event, &auth_events)? { + tracing::info!("starting m.room.power_levels check"); + if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) { + if !required_pwr_lvl { + tracing::warn!("power level was not allowed"); + return Some(false); + } + } else { + tracing::warn!("power level was not allowed"); return Some(false); } tracing::info!("power levels event allowed"); @@ -372,6 +379,13 @@ fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Opt let send_level = get_send_level(event.kind(), event.state_key(), ple); let user_level = get_user_power_level(event.sender(), auth_events); + tracing::debug!( + "{} snd {} usr {}", + event.event_id().unwrap().to_string(), + send_level, + user_level + ); + if user_level < send_level { return Some(false); } @@ -394,16 +408,17 @@ fn check_power_levels( ) -> Option { use itertools::Itertools; - let key = (power_event.kind(), power_event.state_key()?); + let key = (power_event.kind(), power_event.state_key().unwrap()); let current_state = auth_events.get(&key)?; let user_content = power_event .deserialize_content::() - .ok()?; + .unwrap(); let current_content = current_state .deserialize_content::() - .ok()?; + .unwrap(); + tracing::info!("validation of power event finished"); // 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); @@ -424,6 +439,8 @@ fn check_power_levels( event_levels_to_check.push(ev_id); } + tracing::debug!("events to check {:?}", event_levels_to_check); + // 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 @@ -435,6 +452,7 @@ fn check_power_levels( 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 { + tracing::info!("m.room.power_level cannot add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -456,6 +474,7 @@ fn check_power_levels( } if user != power_event.sender() { if old_level.map(|int| (*int).into()) == Some(user_level) { + tracing::info!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } } @@ -463,6 +482,7 @@ fn check_power_levels( 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 { + tracing::info!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -480,6 +500,7 @@ fn check_power_levels( 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 { + tracing::info!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -579,21 +600,30 @@ fn get_send_level( state_key: Option, power_lvl: Option<&StateEvent>, ) -> i64 { + tracing::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { - if state_key.is_some() { - if let Ok(content) = - serde_json::from_value::(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 + if let Ok(content) = + serde_json::from_value::(ple.content()) + { + let mut lvl: i64 = content + .events + .get(&e_type) + .cloned() + .unwrap_or(js_int::int!(50)) + .into(); + let state_def: i64 = content.state_default.into(); + let event_def: i64 = content.events_default.into(); + if state_key.is_some() { + if state_def > lvl { + lvl = event_def; } - content.users_default.into() - } else { - return 50; } + if event_def > lvl { + lvl = event_def; + } + return lvl; } else { - return 0; + return 50; } } else { return 0; diff --git a/src/lib.rs b/src/lib.rs index d0f18cc0..c524b051 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,13 @@ impl StateResolution { // TODO make sure each conflicting event is in event_map?? // synapse says `full_set = {eid for eid in full_conflicted_set if eid in event_map}` all_conflicted.retain(|id| event_map.contains_key(id)); - + println!( + "ALL {:?}", + all_conflicted + .iter() + .map(ToString::to_string) + .collect::>() + ); // get only the power events with a state_key: "" or ban/kick event (sender != state_key) let power_events = all_conflicted .iter() @@ -148,7 +154,7 @@ impl StateResolution { .cloned() .collect::>(); - tracing::debug!( + println!( "POWER {:?}", power_events .iter() @@ -165,7 +171,7 @@ impl StateResolution { &all_conflicted, ); - tracing::debug!( + println!( "SRTD {:?}", sorted_power_levels .iter() @@ -203,7 +209,7 @@ impl StateResolution { .cloned() .collect::>(); - tracing::debug!( + println!( "LEFT {:?}", events_to_resolve .iter() @@ -218,7 +224,7 @@ impl StateResolution { let sorted_left_events = self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); - tracing::debug!( + println!( "SORTED LEFT {:?}", sorted_left_events .iter() @@ -324,7 +330,7 @@ impl StateResolution { 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); - tracing::debug!("{} power level {}", event_id.to_string(), pl); + println!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -424,16 +430,17 @@ impl StateResolution { &self, room_id: &RoomId, event_id: &EventId, - _event_map: &EventMap, // TODO use event_map over store ?? + event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event senders ({}) power level", event_id.to_string()); - + let event = event_map.get(event_id); let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? - for aid in store.auth_event_ids(room_id, &[event_id.clone()]).unwrap() { - if let Ok(aev) = store.get_event(&aid) { + for aid in event_map.get(event_id).unwrap().auth_event_ids() { + println!("aid {}", aid.to_string()); + if let Some(aev) = event_map.get(&aid) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -442,8 +449,10 @@ impl StateResolution { } if pl.is_none() { - for aid in store.auth_event_ids(room_id, &[event_id.clone()]).unwrap() { - if let Ok(aev) = store.get_event(&aid) { + for aid in event_map.get(event_id).unwrap().auth_event_ids() { + println!("aid NONE {}", aid.to_string()); + + if let Some(aev) = event_map.get(&aid) { if aev.is_type_and_key(EventType::RoomCreate, "") { if let Ok(content) = aev .deserialize_content::() @@ -467,7 +476,12 @@ impl StateResolution { }) .flatten() { - content.users_default.into() + if let Some(ev) = event { + if let Some(user) = content.users.get(ev.sender()) { + return (*user).into(); + } + } + content.state_default.into() } else { 0 } @@ -510,6 +524,7 @@ impl StateResolution { } } + tracing::debug!("event to check {:?}", event.event_id().unwrap().to_string()); if !event_auth::auth_check(room_version, &event, auth_events) .ok_or("Auth check failed due to deserialization most likely".to_string()) .unwrap() diff --git a/src/state_event.rs b/src/state_event.rs index d24f5e3b..1a2d0e78 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -35,8 +35,8 @@ impl StateEvent { .contains(&content.membership) { return event.sender.as_str() - // TODO does None here mean the same as state_key = "" - != event.state_key.as_deref().unwrap_or(""); + // TODO is None here a failure + != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); } } @@ -44,32 +44,9 @@ impl StateEvent { } _ => false, }, - Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), - }, - Self::Sync(any_event) => match any_event { - PduStub::RoomV1PduStub(event) => match event.kind { - EventType::RoomPowerLevels - | EventType::RoomJoinRules - | EventType::RoomCreate => event.state_key == Some("".into()), - EventType::RoomMember => { - if let Ok(content) = - serde_json::from_value::(event.content.clone()) - { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() - // TODO does None here mean the same as state_key = "" - != event.state_key.as_deref().unwrap_or(""); - } - } - - false - } - _ => false, - }, - PduStub::RoomV3PduStub(event) => event.state_key == Some("".into()), + Pdu::RoomV3Pdu(event) => panic!(), }, + Self::Sync(any_event) => panic!(), } } pub fn deserialize_content( diff --git a/tests/state_res.rs b/tests/state_res.rs index b3b8fb64..cdfd656d 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -286,10 +286,10 @@ fn INITIAL_EDGES() -> Vec { fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { use itertools::Itertools; - // to activate logging use `RUST_LOG=debug cargo t` - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init(); + // to activate logging use `RUST_LOG=debug cargo t one_test_only` + // tracer::fmt() + // .with_env_filter(tracer::EnvFilter::from_default_env()) + // .init(); let mut resolver = StateResolution::default(); // TODO what do we fill this with, everything ?? @@ -519,6 +519,59 @@ fn ban_vs_power_level() { } // #[test] +fn topic_basic() { + let events = &[ + to_init_pdu_event("T1", alice(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "PA1", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 50}}), + ), + to_init_pdu_event("T2", alice(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "PA2", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 0}}), + ), + to_init_pdu_event( + "PAB", + bobo(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bobo(): 50}}), + ), + to_init_pdu_event("T3", bobo(), EventType::RoomTopic, Some(""), json!({})), + ]; + + let edges = vec![ + vec!["END", "PA2", "T2", "PA1", "T1", "START"], + vec!["END", "T3", "PB", "PA1"], + ] + .into_iter() + .map(|list| { + list.into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap() + }) + .collect::>(); + + let expected_state_ids = vec!["PA2", "T2"] + .into_iter() + .map(|s| format!("${}:foo", s)) + .map(EventId::try_from) + .collect::, _>>() + .unwrap(); + + do_check(events, edges, expected_state_ids) +} + +#[test] fn topic_reset() { let events = &[ to_init_pdu_event("T1", alice(), EventType::RoomTopic, Some(""), json!({})), From 106cab46bc0897674eb4950e747e44dc282f6bcf Mon Sep 17 00:00:00 2001 From: Devin R Date: Thu, 23 Jul 2020 09:38:47 -0400 Subject: [PATCH 012/130] lexi_topo_sort needs to return a neg power_level to sort properly All tests pass!!! Changed println! to logger calls. --- src/lib.rs | 54 ++++++----- src/state_event.rs | 27 +++++- tests/state_res.rs | 219 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 225 insertions(+), 75 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c524b051..aefd24d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,13 +140,15 @@ impl StateResolution { // TODO make sure each conflicting event is in event_map?? // synapse says `full_set = {eid for eid in full_conflicted_set if eid in event_map}` all_conflicted.retain(|id| event_map.contains_key(id)); - println!( + + tracing::debug!( "ALL {:?}", all_conflicted .iter() .map(ToString::to_string) .collect::>() ); + // get only the power events with a state_key: "" or ban/kick event (sender != state_key) let power_events = all_conflicted .iter() @@ -154,7 +156,7 @@ impl StateResolution { .cloned() .collect::>(); - println!( + tracing::debug!( "POWER {:?}", power_events .iter() @@ -171,7 +173,7 @@ impl StateResolution { &all_conflicted, ); - println!( + tracing::debug!( "SRTD {:?}", sorted_power_levels .iter() @@ -209,7 +211,7 @@ impl StateResolution { .cloned() .collect::>(); - println!( + tracing::debug!( "LEFT {:?}", events_to_resolve .iter() @@ -224,7 +226,7 @@ impl StateResolution { let sorted_left_events = self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); - println!( + tracing::debug!( "SORTED LEFT {:?}", sorted_left_events .iter() @@ -330,7 +332,7 @@ impl StateResolution { 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); - println!("{} power level {}", event_id.to_string(), pl); + tracing::info!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -345,10 +347,20 @@ impl StateResolution { // tracing::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); + + tracing::debug!( + "{:?}", + ( + -*pl, + ev.origin_server_ts().clone(), + ev.event_id().unwrap().to_string() + ) + ); + // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (*pl, ev.origin_server_ts().clone(), ev.event_id().cloned()) + (-*pl, ev.origin_server_ts().clone(), ev.event_id().cloned()) }) } @@ -419,16 +431,12 @@ impl StateResolution { sorted.push(node.clone()); } - // tracing::debug!( - // "{:#?}", - // sorted.iter().map(ToString::to_string).collect::>() - // ); sorted } fn get_power_level_for_sender( &self, - room_id: &RoomId, + _room_id: &RoomId, event_id: &EventId, event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, @@ -438,9 +446,8 @@ impl StateResolution { let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? - for aid in event_map.get(event_id).unwrap().auth_event_ids() { - println!("aid {}", aid.to_string()); - if let Some(aev) = event_map.get(&aid) { + for aid in store.get_event(event_id).unwrap().auth_event_ids() { + if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -449,10 +456,8 @@ impl StateResolution { } if pl.is_none() { - for aid in event_map.get(event_id).unwrap().auth_event_ids() { - println!("aid NONE {}", aid.to_string()); - - if let Some(aev) = event_map.get(&aid) { + for aid in store.get_event(event_id).unwrap().auth_event_ids() { + if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomCreate, "") { if let Ok(content) = aev .deserialize_content::() @@ -478,10 +483,11 @@ impl StateResolution { { if let Some(ev) = event { if let Some(user) = content.users.get(ev.sender()) { + tracing::debug!("found {} at power_level {}", ev.sender().to_string(), user); return (*user).into(); } } - content.state_default.into() + content.users_default.into() } else { 0 } @@ -549,7 +555,7 @@ impl StateResolution { /// the `resolved_power_level`. fn mainline_sort( &mut self, - room_id: &RoomId, + _room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, event_map: &EventMap, @@ -571,7 +577,7 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); // We don't need the actual pl_ev here since we delegate to the store - let auth_events = store.auth_event_ids(room_id, &[p]).unwrap(); + let auth_events = store.get_event(&p).unwrap().auth_event_ids(); pl = None; for aid in auth_events { let ev = store.get_event(&aid).unwrap(); @@ -664,7 +670,7 @@ impl StateResolution { fn add_event_and_auth_chain_to_graph( &self, - room_id: &RoomId, + _room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, store: &dyn StateStore, @@ -677,7 +683,7 @@ impl StateResolution { graph.entry(eid.clone()).or_insert(vec![]); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in store.auth_event_ids(room_id, &[eid.clone()]).unwrap() { + for aid in store.get_event(&eid).unwrap().auth_event_ids() { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { state.push(aid.clone()); diff --git a/src/state_event.rs b/src/state_event.rs index 1a2d0e78..f15a72cc 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -44,9 +44,32 @@ impl StateEvent { } _ => false, }, - Pdu::RoomV3Pdu(event) => panic!(), + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), + }, + Self::Sync(any_event) => match any_event { + PduStub::RoomV1PduStub(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + if let Ok(content) = + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) + { + return event.sender.as_str() + // TODO does None here mean the same as state_key = "" + != event.state_key.as_deref().unwrap_or(""); + } + } + + false + } + _ => false, + }, + PduStub::RoomV3PduStub(event) => event.state_key == Some("".into()), }, - Self::Sync(any_event) => panic!(), } } pub fn deserialize_content( diff --git a/tests/state_res.rs b/tests/state_res.rs index cdfd656d..221839d7 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -32,11 +32,11 @@ fn id(id: &str) -> EventId { fn alice() -> UserId { UserId::try_from("@alice:foo").unwrap() } -fn bobo() -> UserId { - UserId::try_from("@bobo:foo").unwrap() +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() } -fn devin() -> UserId { - UserId::try_from("@devin:foo").unwrap() +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() } fn zera() -> UserId { UserId::try_from("@zera:foo").unwrap() @@ -244,16 +244,16 @@ fn INITIAL_EVENTS() -> BTreeMap { ), to_init_pdu_event( "IMB", - bobo(), + bob(), EventType::RoomMember, - Some(bobo().to_string().as_str()), + Some(bob().to_string().as_str()), member_content_join(), ), to_init_pdu_event( "IMC", - devin(), + charlie(), EventType::RoomMember, - Some(devin().to_string().as_str()), + Some(charlie().to_string().as_str()), member_content_join(), ), to_init_pdu_event( @@ -292,7 +292,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // .init(); let mut resolver = StateResolution::default(); - // TODO what do we fill this with, everything ?? + let store = TestStore(RefCell::new( INITIAL_EVENTS() .values() @@ -354,16 +354,16 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .cloned() .collect::>(); - // println!( - // "RESOLVING {:?}", - // state_sets - // .iter() - // .map(|map| map - // .iter() - // .map(|((t, s), id)| (t, s, id.to_string())) - // .collect::>()) - // .collect::>() - // ); + tracing::debug!( + "RESOLVING {:?}", + state_sets + .iter() + .map(|map| map + .iter() + .map(|((t, s), id)| (t, s, id.to_string())) + .collect::>()) + .collect::>() + ); let resolved = resolver.resolve( &room_id(), @@ -397,10 +397,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } let auth_types = state_res::auth_types_for_event(fake_event); - // println!( - // "AUTH TYPES {:?}", - // auth_types.iter().map(|(t, id)| (t, id)).collect::>() - // ); let mut auth_events = vec![]; for key in auth_types { @@ -469,7 +465,7 @@ fn ban_vs_power_level() { alice(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 50}}), + json!({"users": {alice(): 100, bob(): 50}}), ), to_init_pdu_event( "MA", @@ -482,15 +478,15 @@ fn ban_vs_power_level() { "MB", alice(), EventType::RoomMember, - Some(bobo().to_string().as_str()), + Some(bob().to_string().as_str()), member_content_ban(), ), to_init_pdu_event( "PB", - bobo(), + bob(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 50}}), + json!({"users": {alice(): 100, bob(): 50}}), ), ]; @@ -518,7 +514,7 @@ fn ban_vs_power_level() { do_check(events, edges, expected_state_ids) } -// #[test] +#[test] fn topic_basic() { let events = &[ to_init_pdu_event("T1", alice(), EventType::RoomTopic, Some(""), json!({})), @@ -527,7 +523,7 @@ fn topic_basic() { alice(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 50}}), + json!({"users": {alice(): 100, bob(): 50}}), ), to_init_pdu_event("T2", alice(), EventType::RoomTopic, Some(""), json!({})), to_init_pdu_event( @@ -535,16 +531,16 @@ fn topic_basic() { alice(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 0}}), + json!({"users": {alice(): 100, bob(): 0}}), ), to_init_pdu_event( - "PAB", - bobo(), + "PB", + bob(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 50}}), + json!({"users": {alice(): 100, bob(): 50}}), ), - to_init_pdu_event("T3", bobo(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event("T3", bob(), EventType::RoomTopic, Some(""), json!({})), ]; let edges = vec![ @@ -580,14 +576,14 @@ fn topic_reset() { alice(), EventType::RoomPowerLevels, Some(""), - json!({"users": {alice(): 100, bobo(): 50}}), + json!({"users": {alice(): 100, bob(): 50}}), ), - to_init_pdu_event("T2", bobo(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event("T2", bob(), EventType::RoomTopic, Some(""), json!({})), to_init_pdu_event( "MB", alice(), EventType::RoomMember, - Some(bobo().to_string().as_str()), + Some(bob().to_string().as_str()), member_content_ban(), ), ]; @@ -616,6 +612,30 @@ fn topic_reset() { do_check(events, edges, expected_state_ids) } +#[test] +fn test_event_map_none() { + let mut resolver = StateResolution::default(); + + let store = TestStore(RefCell::new(btreemap! {})); + + // build up the DAG + let (state_at_bob, state_at_charlie, expected) = store.set_up(); + + let resolved = match resolver.resolve( + &room_id(), + &RoomVersionId::version_2(), + &[state_at_bob, state_at_charlie], + None, + &store, + ) { + Ok(ResolutionResult::Resolved(state)) => state, + Err(e) => panic!("{}", e), + _ => panic!("conflicted state left"), + }; + + assert_eq!(expected, resolved) +} + #[test] fn test_lexicographical_sort() { let mut resolver = StateResolution::default(); @@ -699,16 +719,10 @@ impl StateStore for TestStore { ) -> Result, String> { use itertools::Itertools; - // println!( - // "EVENTS FOR AUTH {:?}", - // event_ids - // .iter() - // .map(|v| v.iter().map(ToString::to_string).collect::>()) - // .collect::>() - // ); - let mut chains = vec![]; for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained let chain = self .auth_event_ids(room_id, &ids)? .into_iter() @@ -719,10 +733,7 @@ impl StateStore for TestStore { if let Some(chain) = chains.first() { let rest = chains.iter().skip(1).flatten().cloned().collect(); let common = chain.intersection(&rest).collect::>(); - // println!( - // "COMMON {:?}", - // common.iter().map(ToString::to_string).collect::>() - // ); + Ok(chains .iter() .flatten() @@ -736,3 +747,113 @@ impl StateStore for TestStore { } } } + +impl TestStore { + pub fn set_up(&self) -> (StateMap, StateMap, StateMap) { + let create_event = to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ); + let cre = create_event.event_id().unwrap().clone(); + self.0 + .borrow_mut() + .insert(cre.clone(), create_event.clone()); + + let alice_mem = to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &[cre.clone()], + &[cre.clone()], + ); + self.0 + .borrow_mut() + .insert(alice_mem.event_id().unwrap().clone(), alice_mem.clone()); + + let join_rules = to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &[cre.clone(), alice_mem.event_id().unwrap().clone()], + &[alice_mem.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(join_rules.event_id().unwrap().clone(), join_rules.clone()); + + // Bob and Charlie join at the same time, so there is a fork + // this will be represented in the state_sets when we resolve + let bob_mem = to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[join_rules.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(bob_mem.event_id().unwrap().clone(), bob_mem.clone()); + + let charlie_mem = to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[join_rules.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(charlie_mem.event_id().unwrap().clone(), charlie_mem.clone()); + + let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let expected = [ + &create_event, + &alice_mem, + &join_rules, + &bob_mem, + &charlie_mem, + ] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + (state_at_bob, state_at_charlie, expected) + } +} From 29d86ebf3cdc0cb92df502e927bbc133dfe9fe71 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 24 Jul 2020 23:14:30 -0400 Subject: [PATCH 013/130] Fix separate ignoring missing ids and auth_check details --- Cargo.toml | 5 + benches/state_bench.rs | 15 +++ src/event_auth.rs | 162 ++++++++++++++++++++--------- src/lib.rs | 97 +++++++----------- src/state_event.rs | 27 +++-- tests/state_res.rs | 224 +++++++++++++++++++++++++++++------------ 6 files changed, 355 insertions(+), 175 deletions(-) create mode 100644 benches/state_bench.rs diff --git a/Cargo.toml b/Cargo.toml index cb14f3cc..8da3bb0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,8 @@ features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] lazy_static = "1.4.0" +criterion = "0.3.3" + +[[bench]] +name = "state_bench" +harness = false \ No newline at end of file diff --git a/benches/state_bench.rs b/benches/state_bench.rs new file mode 100644 index 00000000..37f7ff88 --- /dev/null +++ b/benches/state_bench.rs @@ -0,0 +1,15 @@ +// `cargo bench` works, but if you use `cargo bench -- --save-baseline ` +// or pass any other args to it, it fails with the error +// `cargo bench unknown option --save-baseline`. +// To pass args to criterion, use this form +// `cargo bench --bench -- --save-baseline `. + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn state_res(c: &mut Criterion) { + c.bench_function("resolve state of 10 events", |b| b.iter(|| {})); +} + +criterion_group!(benches, state_res,); + +criterion_main!(benches); diff --git a/src/event_auth.rs b/src/event_auth.rs index 9459d351..976608e8 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -7,6 +7,7 @@ use ruma::{ }, identifiers::{RoomVersionId, UserId}, }; +use serde_json::json; use crate::{room_version::RoomVersion, state_event::StateEvent, StateMap}; @@ -64,17 +65,39 @@ pub fn auth_check( room_version: &RoomVersionId, event: &StateEvent, auth_events: StateMap, + do_sig_check: bool, ) -> Option { tracing::info!("auth_check beginning"); // don't let power from other rooms be used for auth_event in auth_events.values() { if auth_event.room_id() != event.room_id() { + tracing::info!("found auth event that did not match event's room_id"); return Some(false); } } - // TODO do_sig_check, do_size_check is false when called by `iterative_auth_check` + if do_sig_check { + let sender_domain = event.sender().server_name(); + + let is_invite_via_3pid = if event.kind() == EventType::RoomMember { + event + .deserialize_content::() + .map(|c| c.membership == MembershipState::Invite && c.third_party_invite.is_some()) + .unwrap_or_default() + } else { + false + }; + + if !event.signatures().get(sender_domain).is_some() && !is_invite_via_3pid { + tracing::info!("event not signed by sender's server"); + return Some(false); + } + } + + // TODO do_size_check is false when called by `iterative_auth_check` + // do_size_check is also mostly accomplished by ruma with the exception of checking event_type, + // state_key, and json are below a certain size (255 and 65536 respectivly) // Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules // @@ -148,9 +171,8 @@ pub fn auth_check( if event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if is_membership_change_allowed(event, &auth_events)? { - tracing::info!("m.room.member membership change was allowed"); - return Some(true); + if !is_membership_change_allowed(event, &auth_events)? { + return Some(false); } tracing::info!("m.room.member event was allowed"); @@ -244,10 +266,11 @@ fn is_membership_change_allowed( let target_user_id = UserId::try_from(event.state_key().unwrap()).ok().unwrap(); // if the server_names are different and federation is NOT allowed - if event.room_id().unwrap().server_name() != target_user_id.server_name() { - if !can_federate(auth_events) { - return Some(false); - } + if event.room_id().unwrap().server_name() != target_user_id.server_name() + && !can_federate(auth_events) + { + tracing::info!("server cannot federate"); + return Some(false); } let key = (EventType::RoomMember, event.sender().to_string()); @@ -264,16 +287,18 @@ fn is_membership_change_allowed( 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::() - .ok()? // TODO these are errors? and should be treated as a DB failure? + .ok() + .unwrap() // 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); + let target_level = get_user_power_level(&target_user_id, auth_events); // synapse has a not "what to do for default here 50" let ban_level = get_named_level(auth_events, "ban", 50); @@ -281,7 +306,7 @@ fn is_membership_change_allowed( // TODO clean this up tracing::debug!( "_is_membership_change_allowed: {}", - serde_json::json!({ + serde_json::to_string_pretty(&json!({ "caller_in_room": caller_in_room, "caller_invited": caller_invited, "target_banned": target_banned, @@ -290,12 +315,34 @@ fn is_membership_change_allowed( "join_rule": join_rule, "target_user_id": target_user_id, "event.user_id": event.sender(), - }), + })) + .unwrap(), ); if membership == MembershipState::Invite && content.third_party_invite.is_some() { - // TODO impl this - unimplemented!("third party invite") + // TODO this is unimpled + if !verify_third_party_invite(event, auth_events) { + tracing::info!( + "{} was not invited to this room", + event + .event_id() + .map(ToString::to_string) + .unwrap_or("Unknow".into()) + ); + return Some(false); + } + if target_banned { + tracing::info!( + "{} is banned", + event + .event_id() + .map(ToString::to_string) + .unwrap_or("Unknow".into()) + ); + return Some(false); + } + tracing::info!("invite succeded"); + return Some(true); } if membership != MembershipState::Join { @@ -303,15 +350,27 @@ fn is_membership_change_allowed( && membership == MembershipState::Leave && &target_user_id == event.sender() { + tracing::info!("join event succeded"); return Some(true); } + + if !caller_in_room { + tracing::info!( + "{} is not in this room {:?}", + event.sender(), + event.room_id() + ); + return Some(false); // caller is not joined + } } if membership == MembershipState::Invite { if target_banned { + tracing::info!("target has been banned"); return Some(false); } else if target_in_room { - return Some(false); + tracing::info!("already in room"); + return Some(false); // already in room } else { let invite_level = get_named_level(auth_events, "invite", 0); if user_level < invite_level { @@ -320,39 +379,57 @@ fn is_membership_change_allowed( } } else if membership == MembershipState::Join { if event.sender() != &target_user_id { + tracing::info!("cannot force another user to join"); return Some(false); // cannot force another user to join } else if target_banned { + tracing::info!("cannot join when banned"); return Some(false); // cannot joined when banned } else if join_rule == JoinRule::Public { - // pass + tracing::info!("join rule public") + // pass } else if join_rule == JoinRule::Invite { if !caller_in_room && !caller_invited { + tracing::info!("user has not been invited to this room"); return Some(false); // you are not invited to this room } } else { + tracing::info!("the join rule is Private or yet to be spec'ed by Matrix"); // synapse has 2 TODO's may_join list and private rooms + + // the join_rule is Private or Knock which means it is not yet spec'ed return Some(false); } } else if membership == MembershipState::Leave { if target_banned && user_level < ban_level { + tracing::info!("not enough power to unban"); 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 { + tracing::info!("not enough power to kick user"); return Some(false); // you do not have the power to kick user } } } else if membership == MembershipState::Ban { + tracing::debug!( + "{} < {} || {} <= {}", + user_level, + ban_level, + user_level, + target_level + ); if user_level < ban_level || user_level <= target_level { + tracing::info!("not enough power to ban"); return Some(false); } } else { + tracing::warn!("unknown membership status"); // Unknown membership status return Some(false); } - Some(false) + Some(true) } /// Is the event's sender in the room that they sent the event to. @@ -391,10 +468,8 @@ fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Opt } 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 - } + if sk.starts_with("@") && sk != event.sender().to_string() { + return Some(false); // permission required to post in this room } } Some(true) @@ -467,16 +542,12 @@ fn check_power_levels( 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 old_level.is_some() && new_level.is_some() && old_level == new_level { + continue; } - if user != power_event.sender() { - if old_level.map(|int| (*int).into()) == Some(user_level) { - tracing::info!("m.room.power_level cannot remove ops == to own"); - return Some(false); // cannot remove ops level == to own - } + if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { + tracing::info!("m.room.power_level cannot remove ops == to own"); + return Some(false); // cannot remove ops level == to own } let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level); @@ -491,10 +562,8 @@ fn check_power_levels( 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; - } + if old_level.is_some() && new_level.is_some() && old_level == new_level { + continue; } let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level); @@ -539,7 +608,7 @@ fn check_redaction( fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) -> bool { if let Some(event) = member_event { if let Ok(content) = - serde_json::from_value::(event.content()) + serde_json::from_value::(event.content().clone()) { content.membership == state } else { @@ -602,9 +671,9 @@ fn get_send_level( ) -> i64 { tracing::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { - if let Ok(content) = - serde_json::from_value::(ple.content()) - { + if let Ok(content) = serde_json::from_value::( + ple.content().clone(), + ) { let mut lvl: i64 = content .events .get(&e_type) @@ -613,19 +682,18 @@ fn get_send_level( .into(); let state_def: i64 = content.state_default.into(); let event_def: i64 = content.events_default.into(); - if state_key.is_some() { - if state_def > lvl { - lvl = event_def; - } - } - if event_def > lvl { + if (state_key.is_some() && state_def > lvl) || event_def > lvl { lvl = event_def; } - return lvl; + lvl } else { - return 50; + 50 } } else { - return 0; + 0 } } + +fn verify_third_party_invite(_event: &StateEvent, _auth_events: &StateMap) -> bool { + unimplemented!("impl third party invites") +} diff --git a/src/lib.rs b/src/lib.rs index aefd24d6..04f4cef7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,10 @@ impl StateResolution { // split non-conflicting and conflicting state let (clean, conflicting) = self.separate(&state_sets); + tracing::info!("non conflicting {:?}", clean.len()); + if conflicting.is_empty() { + tracing::warn!("no conflicting state found"); return Ok(ResolutionResult::Resolved(clean)); } @@ -86,6 +89,8 @@ impl StateResolution { // the set of auth events that are not common across server forks let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, &event_map, store)?; + tracing::debug!("auth diff size {}", auth_diff.len()); + // add the auth_diff to conflicting now we have a full set of conflicting events auth_diff.extend(conflicting.values().cloned().flatten()); let mut all_conflicted = auth_diff @@ -94,14 +99,6 @@ impl StateResolution { .into_iter() .collect::>(); - tracing::debug!( - "FULL CONF {:?}", - all_conflicted - .iter() - .map(ToString::to_string) - .collect::>() - ); - tracing::info!("full conflicted set is {} events", all_conflicted.len()); // gather missing events for the event_map @@ -123,6 +120,8 @@ impl StateResolution { .flat_map(|ev| Some((ev.event_id()?.clone(), ev))), ); + tracing::debug!("event map size: {}", event_map.len()); + for event in event_map.values() { if event.room_id() != Some(room_id) { return Err(format!( @@ -139,16 +138,10 @@ impl StateResolution { // TODO make sure each conflicting event is in event_map?? // synapse says `full_set = {eid for eid in full_conflicted_set if eid in event_map}` + // + // don't honor events we cannot "verify" all_conflicted.retain(|id| event_map.contains_key(id)); - tracing::debug!( - "ALL {:?}", - all_conflicted - .iter() - .map(ToString::to_string) - .collect::>() - ); - // get only the power events with a state_key: "" or ban/kick event (sender != state_key) let power_events = all_conflicted .iter() @@ -156,14 +149,6 @@ impl StateResolution { .cloned() .collect::>(); - tracing::debug!( - "POWER {:?}", - power_events - .iter() - .map(ToString::to_string) - .collect::>() - ); - // sort the power events based on power_level/clock/event_id and outgoing/incoming edges let mut sorted_power_levels = self.reverse_topological_power_sort( room_id, @@ -204,7 +189,7 @@ impl StateResolution { 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 + // we have resolved the power events so remove them, I'm sure there are other reasons to do so let events_to_resolve = all_conflicted .iter() .filter(|id| !deduped_power_ev.contains(id)) @@ -267,18 +252,28 @@ impl StateResolution { let mut unconflicted_state = StateMap::new(); let mut conflicted_state = StateMap::new(); - for key in state_sets.iter().flat_map(|map| map.keys()) { + for key in state_sets + .iter() + .flat_map(|map| map.keys()) + .collect::>() + { let mut event_ids = state_sets .iter() - .flat_map(|map| map.get(key).cloned()) + .map(|state_set| state_set.get(key)) .dedup() - .collect::>(); + .collect::>(); if event_ids.len() == 1 { - // unwrap is ok since we know the len is 1 - unconflicted_state.insert(key.clone(), event_ids.pop().unwrap()); + if let Some(Some(id)) = event_ids.pop() { + unconflicted_state.insert(key.clone(), id.clone()); + } else { + panic!() + } } else { - conflicted_state.insert(key.clone(), event_ids); + conflicted_state.insert( + key.clone(), + event_ids.into_iter().flatten().cloned().collect::>(), + ); } } @@ -348,15 +343,6 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!( - "{:?}", - ( - -*pl, - ev.origin_server_ts().clone(), - ev.event_id().unwrap().to_string() - ) - ); - // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. @@ -531,15 +517,17 @@ impl StateResolution { } tracing::debug!("event to check {:?}", event.event_id().unwrap().to_string()); - if !event_auth::auth_check(room_version, &event, auth_events) - .ok_or("Auth check failed due to deserialization most likely".to_string()) - .unwrap() + if event_auth::auth_check(room_version, &event, auth_events, false) + .ok_or("Auth check failed due to deserialization most likely".to_string())? { - // TODO synapse passes here on AuthError ?? - tracing::warn!("event {} failed the authentication", event_id.to_string()); - } else { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key().unwrap()), event_id.clone()); + } else { + // TODO synapse passes here on AuthError ?? + tracing::warn!( + "event {} failed the authentication check", + event_id.to_string() + ); } // We yield occasionally when we're working with large data sets to @@ -576,13 +564,15 @@ impl StateResolution { let mut idx = 0; while let Some(p) = pl { mainline.push(p.clone()); + // We don't need the actual pl_ev here since we delegate to the store - let auth_events = store.get_event(&p).unwrap().auth_event_ids(); + let event = store.get_event(&p).unwrap(); + let auth_events = event.auth_event_ids(); pl = None; for aid in auth_events { let ev = store.get_event(&aid).unwrap(); if ev.is_type_and_key(EventType::RoomPowerLevels, "") { - pl = Some(aid); + pl = Some(aid.clone()); break; } } @@ -646,16 +636,7 @@ impl StateResolution { } let auth_events = sort_ev.auth_event_ids(); - tracing::debug!( - "mainline AUTH EV {:?}", - auth_events - .iter() - .map(ToString::to_string) - .collect::>() - ); - event = None; - for aid in auth_events { let aev = store.get_event(&aid).unwrap(); if aev.is_type_and_key(EventType::RoomPowerLevels, "") { @@ -690,7 +671,7 @@ impl StateResolution { } // we just inserted this at the start of the while loop - graph.get_mut(&eid).unwrap().push(aid); + graph.get_mut(&eid).unwrap().push(aid.clone()); } } } diff --git a/src/state_event.rs b/src/state_event.rs index f15a72cc..897834f6 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use ruma::{ events::{ from_raw_json_value, @@ -5,7 +7,7 @@ use ruma::{ room::member::{MemberEventContent, MembershipState}, EventDeHelper, EventType, }, - identifiers::{EventId, RoomId, UserId}, + identifiers::{EventId, RoomId, ServerName, UserId}, }; use serde::{de, Serialize}; use serde_json::value::RawValue as RawJsonValue; @@ -198,15 +200,28 @@ impl StateEvent { } } - pub fn content(&self) -> serde_json::Value { + 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(), + Pdu::RoomV1Pdu(ev) => &ev.content, + Pdu::RoomV3Pdu(ev) => &ev.content, }, Self::Sync(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.content.clone(), - PduStub::RoomV3PduStub(ev) => ev.content.clone(), + PduStub::RoomV1PduStub(ev) => &ev.content, + PduStub::RoomV3PduStub(ev) => &ev.content, + }, + } + } + + pub fn signatures(&self) -> BTreeMap, BTreeMap> { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, + Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => ev.signatures.clone(), + PduStub::RoomV3PduStub(ev) => ev.signatures.clone(), }, } } diff --git a/tests/state_res.rs b/tests/state_res.rs index 221839d7..ac5541f9 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -23,9 +23,16 @@ use serde_json::{from_value as from_json_value, json, Value as JsonValue}; use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; use tracing_subscriber as tracer; +use std::sync::Once; + +static LOGGER: Once = Once::new(); + static mut SERVER_TIMESTAMP: i32 = 0; -fn id(id: &str) -> EventId { +fn event_id(id: &str) -> EventId { + if id.contains("$") { + return EventId::try_from(id).unwrap(); + } EventId::try_from(format!("${}:foo", id)).unwrap() } @@ -38,6 +45,9 @@ fn bob() -> UserId { fn charlie() -> UserId { UserId::try_from("@charlie:foo").unwrap() } +fn ella() -> UserId { + UserId::try_from("@ella:foo").unwrap() +} fn zera() -> UserId { UserId::try_from("@zera:foo").unwrap() } @@ -277,19 +287,19 @@ fn INITIAL_EDGES() -> Vec { "START", "IMZ", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE", ] .into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap() + .map(event_id) + .collect::>() } fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { use itertools::Itertools; // to activate logging use `RUST_LOG=debug cargo t one_test_only` - // tracer::fmt() - // .with_env_filter(tracer::EnvFilter::from_default_env()) - // .init(); + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); let mut resolver = StateResolution::default(); @@ -354,17 +364,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .cloned() .collect::>(); - tracing::debug!( - "RESOLVING {:?}", - state_sets - .iter() - .map(|map| map - .iter() - .map(|((t, s), id)| (t, s, id.to_string())) - .collect::>()) - .collect::>() - ); - let resolved = resolver.resolve( &room_id(), &RoomVersionId::version_1(), @@ -389,6 +388,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: }; let mut state_after = state_before.clone(); + if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); // we know there is a state_key unwrap OK @@ -414,12 +414,16 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: e.sender().clone(), e.kind(), e.state_key().as_deref(), - e.content(), + e.content().clone(), &auth_events, prev_events, ); - // we have to update our store, an actual user of this lib would do this - // with the result of the resolution> + // we have to update our store, an actual user of this lib would + // be giving us state from a DB. + // + // TODO + // TODO we need to convert the `StateResolution::resolve` to use the event_map + // because the user of this crate cannot update their DB's state. *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); @@ -442,12 +446,10 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: expected_state.insert(key, node); } - let start_state = state_at_event - .get(&EventId::try_from("$START:foo").unwrap()) - .unwrap(); + let start_state = state_at_event.get(&event_id("$START:foo")).unwrap(); let end_state = state_at_event - .get(&EventId::try_from("$END:foo").unwrap()) + .get(&event_id("$END:foo")) .unwrap() .iter() .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) @@ -495,21 +497,13 @@ fn ban_vs_power_level() { vec!["END", "PB", "PA"], ] .into_iter() - .map(|list| { - list.into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap() - }) + .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); let expected_state_ids = vec!["PA", "MA", "MB"] .into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); do_check(events, edges, expected_state_ids) } @@ -548,21 +542,13 @@ fn topic_basic() { vec!["END", "T3", "PB", "PA1"], ] .into_iter() - .map(|list| { - list.into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap() - }) + .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); let expected_state_ids = vec!["PA2", "T2"] .into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); do_check(events, edges, expected_state_ids) } @@ -593,21 +579,125 @@ fn topic_reset() { vec!["END", "T1"], ] .into_iter() - .map(|list| { - list.into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap() - }) + .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); let expected_state_ids = vec!["T1", "MB", "PA"] .into_iter() - .map(|s| format!("${}:foo", s)) - .map(EventId::try_from) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); + + do_check(events, edges, expected_state_ids) +} + +#[test] +fn join_rule_evasion() { + let events = &[ + to_init_pdu_event( + "JR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Private }), + ), + to_init_pdu_event( + "ME", + ella(), + EventType::RoomMember, + Some(ella().to_string().as_str()), + member_content_join(), + ), + ]; + + let edges = vec![vec!["END", "JR", "START"], vec!["END", "ME", "START"]] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec![event_id("JR")]; + + do_check(events, edges, expected_state_ids) +} + +#[test] +fn offtopic_power_level() { + let events = &[ + to_init_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + ), + to_init_pdu_event( + "PB", + bob(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50, charlie(): 50}}), + ), + to_init_pdu_event( + "PC", + charlie(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50, charlie(): 0}}), + ), + ]; + + let edges = vec![vec!["END", "PC", "PB", "PA", "START"], vec!["END", "PA"]] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec!["PC"].into_iter().map(event_id).collect::>(); + + do_check(events, edges, expected_state_ids) +} + +#[test] +fn topic_setting() { + let events = &[ + to_init_pdu_event("T1", alice(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "PA1", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + ), + to_init_pdu_event("T2", alice(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event( + "PA2", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 0}}), + ), + to_init_pdu_event( + "PB", + bob(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + ), + to_init_pdu_event("T3", bob(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event("MZ1", zera(), EventType::RoomMessage, None, json!({})), + to_init_pdu_event("T4", alice(), EventType::RoomTopic, Some(""), json!({})), + ]; + + let edges = vec![ + vec!["END", "T4", "MZ1", "PA2", "T2", "PA1", "T1", "START"], + vec!["END", "MZ1", "T3", "PB", "PA1"], + ] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec!["T4", "PA2"] + .into_iter() + .map(event_id) + .collect::>(); do_check(events, edges, expected_state_ids) } @@ -641,11 +731,11 @@ fn test_lexicographical_sort() { let mut resolver = StateResolution::default(); let graph = btreemap! { - id("l") => vec![id("o")], - id("m") => vec![id("n"), id("o")], - id("n") => vec![id("o")], - id("o") => vec![], // "o" has zero outgoing edges but 4 incoming edges - id("p") => vec![id("o")], + event_id("l") => vec![event_id("o")], + event_id("m") => vec![event_id("n"), event_id("o")], + event_id("n") => vec![event_id("o")], + event_id("o") => vec![], // "o" has zero outgoing edges but 4 incoming edges + event_id("p") => vec![event_id("o")], }; let res = @@ -750,6 +840,12 @@ impl StateStore for TestStore { impl TestStore { pub fn set_up(&self) -> (StateMap, StateMap, StateMap) { + // to activate logging use `RUST_LOG=debug cargo t one_test_only` + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); let create_event = to_pdu_event::( "CREATE", alice(), From 0ae8c8fe0946e095b7bd76074dd27319a3d03785 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 25 Jul 2020 00:03:33 -0400 Subject: [PATCH 014/130] Add benchmarks for lexi_topo and state resolve method --- benches/state_bench.rs | 380 ++++++++++++++++++++++++++++++++++++++++- tests/state_res.rs | 2 - 2 files changed, 377 insertions(+), 5 deletions(-) diff --git a/benches/state_bench.rs b/benches/state_bench.rs index 37f7ff88..df152bfa 100644 --- a/benches/state_bench.rs +++ b/benches/state_bench.rs @@ -3,13 +3,387 @@ // `cargo bench unknown option --save-baseline`. // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + convert::TryFrom, + time::UNIX_EPOCH, +}; use criterion::{criterion_group, criterion_main, Criterion}; +use maplit::btreemap; +use ruma::{ + events::{ + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +use serde_json::{json, Value as JsonValue}; +use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; -fn state_res(c: &mut Criterion) { - c.bench_function("resolve state of 10 events", |b| b.iter(|| {})); +static mut SERVER_TIMESTAMP: i32 = 0; + +fn lexico_topo_sort(c: &mut Criterion) { + c.bench_function("lexicographical topological sort", |b| { + let graph = btreemap! { + event_id("l") => vec![event_id("o")], + event_id("m") => vec![event_id("n"), event_id("o")], + event_id("n") => vec![event_id("o")], + event_id("o") => vec![], // "o" has zero outgoing edges but 4 incoming edges + event_id("p") => vec![event_id("o")], + }; + b.iter(|| { + let mut resolver = StateResolution::default(); + + let _ = resolver + .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))); + }) + }); } -criterion_group!(benches, state_res,); +fn resolution_shallow_auth_chain(c: &mut Criterion) { + c.bench_function("resolve state of 5 events one fork", |b| { + let mut resolver = StateResolution::default(); + + let store = TestStore(RefCell::new(btreemap! {})); + + // build up the DAG + let (state_at_bob, state_at_charlie, _) = store.set_up(); + + b.iter(|| { + let _resolved = match resolver.resolve( + &room_id(), + &RoomVersionId::version_2(), + &[state_at_bob.clone(), state_at_charlie.clone()], + None, + &store, + ) { + Ok(ResolutionResult::Resolved(state)) => state, + Err(e) => panic!("{}", e), + _ => panic!("conflicted state left"), + }; + }) + }); +} + +criterion_group!(benches, lexico_topo_sort, resolution_shallow_auth_chain); criterion_main!(benches); + +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_events(&self, events: &[EventId]) -> Result, String> { + Ok(self + .0 + .borrow() + .iter() + .filter(|e| events.contains(e.0)) + .map(|(_, s)| s) + .cloned() + .collect()) + } + + fn get_event(&self, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } + + fn auth_event_ids( + &self, + room_id: &RoomId, + event_ids: &[EventId], + ) -> Result, String> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(&ev_id).unwrap(); + stack.extend(event.auth_event_ids()); + } + + Ok(result) + } + + fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: Vec>, + ) -> Result, String> { + use itertools::Itertools; + + let mut chains = vec![]; + for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } +} + +impl TestStore { + pub fn set_up(&self) -> (StateMap, StateMap, StateMap) { + let create_event = to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ); + let cre = create_event.event_id().unwrap().clone(); + self.0 + .borrow_mut() + .insert(cre.clone(), create_event.clone()); + + let alice_mem = to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &[cre.clone()], + &[cre.clone()], + ); + self.0 + .borrow_mut() + .insert(alice_mem.event_id().unwrap().clone(), alice_mem.clone()); + + let join_rules = to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &[cre.clone(), alice_mem.event_id().unwrap().clone()], + &[alice_mem.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(join_rules.event_id().unwrap().clone(), join_rules.clone()); + + // Bob and Charlie join at the same time, so there is a fork + // this will be represented in the state_sets when we resolve + let bob_mem = to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[join_rules.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(bob_mem.event_id().unwrap().clone(), bob_mem.clone()); + + let charlie_mem = to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[join_rules.event_id().unwrap().clone()], + ); + self.0 + .borrow_mut() + .insert(charlie_mem.event_id().unwrap().clone(), charlie_mem.clone()); + + let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let expected = [ + &create_event, + &alice_mem, + &join_rules, + &bob_mem, + &charlie_mem, + ] + .iter() + .map(|e| { + ( + (e.kind(), e.state_key().unwrap().clone()), + e.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + (state_at_bob, state_at_charlie, expected) + } +} + +fn event_id(id: &str) -> EventId { + if id.contains("$") { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} + +fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains("$") { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(|s| { + EventId::try_from( + if s.contains("$") { + s.to_owned() + } else { + format!("${}:foo", s) + } + .as_str(), + ) + }) + .collect::, _>>() + .unwrap(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(|s| { + EventId::try_from( + if s.contains("$") { + s.to_owned() + } else { + format!("${}:foo", s) + } + .as_str(), + ) + }) + .collect::, _>>() + .unwrap(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} diff --git a/tests/state_res.rs b/tests/state_res.rs index ac5541f9..05dc24cb 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - use std::{ cell::RefCell, collections::{BTreeMap, BTreeSet}, From ea0b6ad5300d07623c9d36c4f722cbad9b9b9ed0 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 25 Jul 2020 08:10:55 -0400 Subject: [PATCH 015/130] Update readme to more accuratly reflect API --- README.md | 28 +++++++++++++++++++--------- tests/state_res.rs | 7 ++----- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d5a37561..b1365819 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,34 @@ Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of ```rust -// The would need to be Serialize/Deserialize to save state -struct StateResV2 { +/// StateMap is just a wrapper/deserialize target for a PDU. +struct StateEvent { + content: serde_json::Value, + room_id: RoomId, + event_id: EventId, + // ... and so on +} + +/// A mapping of event type and state_key to some value `T`, usually an `EventId`. +pub type StateMap = BTreeMap<(EventType, String), T>; + + +struct StateResolution { // Should any information be kept or should all of it be fetched from the - // StateStore trait?, - state_graph: Something, + // StateStore trait? + event_map: BTreeMap, // fields for temp storage during resolution?? + /// The events that conflict and their auth chains. conflicting_events: StateMap>, } -impl StateResV2 { - /// The point of this all add nonconflicting events to the graph - /// and resolve and add conflicting events. +impl StateResolution { + /// The point of this all. Resolve the conflicting set of . fn resolve(&mut self, events: Vec>) -> StateMap { } } -// The tricky part of making a good abstraction +// The tricky part, making a good abstraction... trait StateStore { /// Return a single event based on the EventId. fn get_event(&self, event_id: &EventId) -> Result; @@ -27,7 +38,6 @@ trait StateStore { /// Returns a Vec of the related auth events to the given `event`. fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result, String>; - } ``` diff --git a/tests/state_res.rs b/tests/state_res.rs index 05dc24cb..e36f30ac 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -2,13 +2,12 @@ use std::{ cell::RefCell, collections::{BTreeMap, BTreeSet}, convert::TryFrom, - time::{Duration, SystemTime, UNIX_EPOCH}, + time::UNIX_EPOCH, }; use maplit::btreemap; use ruma::{ events::{ - pdu::Pdu, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -17,7 +16,7 @@ use ruma::{ }, identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; -use serde_json::{from_value as from_json_value, json, Value as JsonValue}; +use serde_json::{json, Value as JsonValue}; use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; use tracing_subscriber as tracer; @@ -290,8 +289,6 @@ fn INITIAL_EDGES() -> Vec { } fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { - use itertools::Itertools; - // to activate logging use `RUST_LOG=debug cargo t one_test_only` let _ = LOGGER.call_once(|| { tracer::fmt() From d8fb5ca112167a7851126db1a11d0c5978bf19dd Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 27 Jul 2020 00:09:21 -0400 Subject: [PATCH 016/130] Add benchmark for longer auth chain and Error type This required that the code being run in the benchmark be tested to verify it works correctly. Now work can begin cleaning up and optimizing state-res. --- Cargo.toml | 1 + README.md | 9 +- benches/state_bench.rs | 259 ++++++++++++-- src/error.rs | 23 ++ src/event_auth.rs | 65 +++- src/lib.rs | 109 ++++-- src/state_event.rs | 12 +- tests/auth_ids.rs | 741 +++++++++++++++++++++++++++++++++++++++++ tests/state_res.rs | 69 ++-- 9 files changed, 1171 insertions(+), 117 deletions(-) create mode 100644 src/error.rs create mode 100644 tests/auth_ids.rs diff --git a/Cargo.toml b/Cargo.toml index 8da3bb0b..53d0c68e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.56" tracing = "0.1.16" maplit = "1.0.2" +thiserror = "1.0.20" tracing-subscriber = "0.2.8" [dependencies.ruma] diff --git a/README.md b/README.md index b1365819..f751df51 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of +### Matrix state resolution in rust! + ```rust /// StateMap is just a wrapper/deserialize target for a PDU. struct StateEvent { @@ -41,3 +42,9 @@ trait StateStore { } ``` + + + +The `StateStore` trait is an abstraction around what ever database your server (or maybe even client) uses to store __P__[]()ersistant __D__[]()ata __U__[]()nits. + +We use `ruma`s types when deserializing any PDU or it's contents which helps avoid a lot of type checking logic [synapse](https://github.com/matrix-org/synapse) must do while authenticating event chains. \ No newline at end of file diff --git a/benches/state_bench.rs b/benches/state_bench.rs index df152bfa..c4fa0e40 100644 --- a/benches/state_bench.rs +++ b/benches/state_bench.rs @@ -70,10 +70,83 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { }); } -criterion_group!(benches, lexico_topo_sort, resolution_shallow_auth_chain); +fn resolve_deeper_event_set(c: &mut Criterion) { + c.bench_function("resolve state of 10 events 3 conflicting", |b| { + let mut resolver = StateResolution::default(); + + let init = INITIAL_EVENTS(); + let ban = BAN_STATE_SET(); + + let mut inner = init; + inner.extend(ban); + let store = TestStore(RefCell::new(inner.clone())); + + let state_set_a = [ + inner.get(&event_id("CREATE")).unwrap(), + inner.get(&event_id("IJR")).unwrap(), + inner.get(&event_id("IMA")).unwrap(), + inner.get(&event_id("IMB")).unwrap(), + inner.get(&event_id("IMC")).unwrap(), + inner.get(&event_id("MB")).unwrap(), + inner.get(&event_id("PA")).unwrap(), + ] + .iter() + .map(|ev| { + ( + (ev.kind(), ev.state_key().unwrap()), + ev.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let state_set_b = [ + inner.get(&event_id("CREATE")).unwrap(), + inner.get(&event_id("IJR")).unwrap(), + inner.get(&event_id("IMA")).unwrap(), + inner.get(&event_id("IMB")).unwrap(), + inner.get(&event_id("IMC")).unwrap(), + inner.get(&event_id("IME")).unwrap(), + inner.get(&event_id("PA")).unwrap(), + ] + .iter() + .map(|ev| { + ( + (ev.kind(), ev.state_key().unwrap()), + ev.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + b.iter(|| { + let _resolved = match resolver.resolve( + &room_id(), + &RoomVersionId::version_2(), + &[state_set_a.clone(), state_set_b.clone()], + Some(inner.clone()), + &store, + ) { + Ok(ResolutionResult::Resolved(state)) => state, + Err(_) => panic!("resolution failed during benchmarking"), + _ => panic!("resolution failed during benchmarking"), + }; + }) + }); +} + +criterion_group!( + benches, + lexico_topo_sort, + resolution_shallow_auth_chain, + resolve_deeper_event_set +); criterion_main!(benches); +//*///////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION DETAILS AHEAD +// +/////////////////////////////////////////////////////////////////////*/ pub struct TestStore(RefCell>); #[allow(unused)] @@ -115,7 +188,7 @@ impl StateStore for TestStore { result.push(ev_id.clone()); let event = self.get_event(&ev_id).unwrap(); - stack.extend(event.auth_event_ids()); + stack.extend(event.auth_events()); } Ok(result) @@ -220,7 +293,7 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[cre, join_rules.event_id().unwrap().clone()], &[join_rules.event_id().unwrap().clone()], ); self.0 @@ -231,7 +304,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) @@ -241,7 +314,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) @@ -257,7 +330,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) @@ -268,7 +341,7 @@ impl TestStore { } fn event_id(id: &str) -> EventId { - if id.contains("$") { + if id.contains('$') { return EventId::try_from(id).unwrap(); } EventId::try_from(format!("${}:foo", id)).unwrap() @@ -283,11 +356,25 @@ fn bob() -> UserId { fn charlie() -> UserId { UserId::try_from("@charlie:foo").unwrap() } +fn ella() -> UserId { + UserId::try_from("@ella:foo").unwrap() +} fn room_id() -> RoomId { RoomId::try_from("!test:foo").unwrap() } +fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + fn member_content_join() -> JsonValue { serde_json::to_value(MemberEventContent { membership: MembershipState::Join, @@ -317,7 +404,7 @@ where SERVER_TIMESTAMP += 1; ts }; - let id = if id.contains("$") { + let id = if id.contains('$') { id.to_string() } else { format!("${}:foo", id) @@ -325,33 +412,13 @@ where let auth_events = auth_events .iter() .map(AsRef::as_ref) - .map(|s| { - EventId::try_from( - if s.contains("$") { - s.to_owned() - } else { - format!("${}:foo", s) - } - .as_str(), - ) - }) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) - .map(|s| { - EventId::try_from( - if s.contains("$") { - s.to_owned() - } else { - format!("${}:foo", s) - } - .as_str(), - ) - }) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); let json = if let Some(state_key) = state_key { json!({ @@ -387,3 +454,131 @@ where }; serde_json::from_value(json).unwrap() } + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + to_pdu_event::( + "START", + charlie(), + EventType::RoomMessage, + None, + json!({}), + &[], + &[], + ), + to_pdu_event::( + "END", + charlie(), + EventType::RoomMessage, + None, + json!({}), + &[], + &[], + ), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn BAN_STATE_SET() -> BTreeMap { + vec![ + to_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], // auth_events + &["START"], // prev_events + ), + to_pdu_event( + "PB", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], + &["END"], + ), + to_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_ban(), + &["CREATE", "IMA", "PB"], + &["PA"], + ), + to_pdu_event( + "IME", + ella(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_join(), + &["CREATE", "IJR", "PA"], + &["MB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..79f91750 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,23 @@ +use std::num::ParseIntError; + +use serde_json::Error as JsonError; +use thiserror::Error; + +/// Result type for state resolution. +pub type Result = std::result::Result; + +/// Represents the various errors that arise when resolving state. +#[derive(Error, Debug)] +pub enum Error { + /// A deserialization error. + #[error(transparent)] + SerdeJson(#[from] JsonError), + + /// An error that occurs when converting from JSON numbers to rust. + #[error(transparent)] + IntParseError(#[from] ParseIntError), + + // TODO remove once the correct errors are used + #[error("an error occured {0}")] + TempString(String), +} diff --git a/src/event_auth.rs b/src/event_auth.rs index 976608e8..8fad3fdd 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; +use maplit::btreeset; use ruma::{ events::{ room::{self, join_rules::JoinRule, member::MembershipState}, @@ -89,7 +90,7 @@ pub fn auth_check( false }; - if !event.signatures().get(sender_domain).is_some() && !is_invite_via_3pid { + if event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { tracing::info!("event not signed by sender's server"); return Some(false); } @@ -107,6 +108,7 @@ pub fn auth_check( // domain of room_id must match domain of sender. if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { + tracing::info!("creation events server does not match sender"); return Some(false); // creation events room id does not match senders } @@ -117,7 +119,8 @@ pub fn auth_check( .content() .get("room_version") .cloned() - .unwrap_or(serde_json::json!({})), + // synapse defaults to version 1 + .unwrap_or(serde_json::json!("1")), ) .is_err() { @@ -231,7 +234,7 @@ fn can_federate(auth_events: &StateMap) -> 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" + fed == "true" } else { false } @@ -468,7 +471,7 @@ fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Opt } if let Some(sk) = event.state_key() { - if sk.starts_with("@") && sk != event.sender().to_string() { + if sk.starts_with('@') && sk != event.sender().as_str() { return Some(false); // permission required to post in this room } } @@ -484,7 +487,13 @@ fn check_power_levels( use itertools::Itertools; let key = (power_event.kind(), power_event.state_key().unwrap()); - let current_state = auth_events.get(&key)?; + + let current_state = if let Some(current_state) = auth_events.get(&key) { + current_state + } else { + // TODO synapse returns here, shouldn't this be an error ?? + return Some(true); + }; let user_content = power_event .deserialize_content::() @@ -493,25 +502,27 @@ fn check_power_levels( .deserialize_content::() .unwrap(); - tracing::info!("validation of power event finished"); // validation of users is done in Ruma, synapse for loops validating user_ids and integers here + tracing::info!("validation of power event finished"); let user_level = get_user_power_level(power_event.sender(), auth_events); - let mut user_levels_to_check = vec![]; + let mut user_levels_to_check = btreeset![]; 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); + user_levels_to_check.insert(user); } - let mut event_levels_to_check = vec![]; + tracing::debug!("users to check {:?}", user_levels_to_check); + + let mut event_levels_to_check = btreeset![]; 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); + event_levels_to_check.insert(ev_id); } tracing::debug!("events to check {:?}", event_levels_to_check); @@ -574,9 +585,43 @@ fn check_power_levels( } } + let levels = [ + "users_default", + "events_default", + "state_default", + "ban", + "redact", + "kick", + "invite", + ]; + let old_state = serde_json::to_value(old_state).unwrap(); + let new_state = serde_json::to_value(new_state).unwrap(); + for lvl_name in &levels { + if let Some((old_lvl, new_lvl)) = get_deserialize_levels(&old_state, &new_state, lvl_name) { + let old_level_too_big = old_lvl > user_level; + let new_level_too_big = new_lvl > user_level; + + if old_level_too_big || new_level_too_big { + tracing::info!("cannot add ops > than own"); + return Some(false); + } + } + } + Some(true) } +fn get_deserialize_levels( + old: &serde_json::Value, + new: &serde_json::Value, + name: &str, +) -> Option<(i64, i64)> { + Some(( + serde_json::from_value(old.get(name)?.clone()).ok()?, + serde_json::from_value(new.get(name)?.clone()).ok()?, + )) +} + /// Does the event redacting come from a user with enough power to redact the given event. fn check_redaction( room_version: &RoomVersionId, diff --git a/src/lib.rs b/src/lib.rs index 04f4cef7..e5f9da55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::or_fun_call)] + use std::{ cmp::Reverse, collections::{BTreeMap, BTreeSet, BinaryHeap}, @@ -11,11 +13,13 @@ use ruma::{ }; use serde::{Deserialize, Serialize}; +mod error; mod event_auth; mod room_version; mod state_event; mod state_store; +pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; pub use state_event::StateEvent; pub use state_store::StateStore; @@ -66,7 +70,7 @@ impl StateResolution { event_map: Option>, store: &dyn StateStore, // TODO actual error handling (`thiserror`??) - ) -> Result { + ) -> Result { tracing::info!("State resolution starting"); let mut event_map = if let Some(ev_map) = event_map { @@ -76,7 +80,26 @@ impl StateResolution { }; // split non-conflicting and conflicting state let (clean, conflicting) = self.separate(&state_sets); + tracing::debug!( + "CLEAN {:#?}", + clean + .iter() + .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .collect::>() + ); + tracing::debug!( + "CONFLICT {:#?}", + conflicting + .iter() + .map(|((ty, key), ids)| format!( + "(({} `{}`), {:?})", + ty, + key, + ids.iter().map(ToString::to_string).collect::>() + )) + .collect::>() + ); tracing::info!("non conflicting {:?}", clean.len()); if conflicting.is_empty() { @@ -124,7 +147,7 @@ impl StateResolution { for event in event_map.values() { if event.room_id() != Some(room_id) { - return Err(format!( + return Err(Error::TempString(format!( "resolving event {} in room {}, when correct room is {}", event .event_id() @@ -132,7 +155,7 @@ impl StateResolution { .unwrap_or("`unknown`"), event.room_id().map(|id| id.as_str()).unwrap_or("`unknown`"), room_id.as_str() - )); + ))); } } @@ -153,7 +176,7 @@ impl StateResolution { let mut sorted_power_levels = self.reverse_topological_power_sort( room_id, &power_events, - &mut event_map, + &event_map, // TODO use event_map store, &all_conflicted, ); @@ -172,7 +195,7 @@ impl StateResolution { room_version, &sorted_power_levels, &clean, - &mut event_map, + &event_map, store, )?; @@ -224,7 +247,7 @@ impl StateResolution { room_version, &sorted_left_events, &resolved, - &mut event_map, + &event_map, store, )?; @@ -255,7 +278,8 @@ impl StateResolution { for key in state_sets .iter() .flat_map(|map| map.keys()) - .collect::>() + .dedup() + .collect::>() { let mut event_ids = state_sets .iter() @@ -263,6 +287,14 @@ impl StateResolution { .dedup() .collect::>(); + tracing::debug!( + "SEP {:?}", + event_ids + .iter() + .map(|i| i.map(ToString::to_string).unwrap_or("None".into())) + .collect::>() + ); + if event_ids.len() == 1 { if let Some(Some(id)) = event_ids.pop() { unconflicted_state.insert(key.clone(), id.clone()); @@ -270,6 +302,7 @@ impl StateResolution { panic!() } } else { + tracing::warn!("{:?}", key); conflicted_state.insert( key.clone(), event_ids.into_iter().flatten().cloned().collect::>(), @@ -287,19 +320,21 @@ impl StateResolution { state_sets: &[StateMap], _event_map: &EventMap, store: &dyn StateStore, - ) -> Result, String> { + ) -> Result> { use itertools::Itertools; tracing::debug!("calculating auth chain difference"); - store.auth_chain_diff( - room_id, - state_sets - .iter() - .map(|map| map.values().cloned().collect()) - .dedup() - .collect::>(), - ) + store + .auth_chain_diff( + room_id, + state_sets + .iter() + .map(|map| map.values().cloned().collect()) + .dedup() + .collect::>(), + ) + .map_err(Error::TempString) } pub fn reverse_topological_power_sort( @@ -338,15 +373,20 @@ impl StateResolution { } } - self.lexicographical_topological_sort(&mut graph, |event_id| { + self.lexicographical_topological_sort(&graph, |event_id| { // tracing::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); + tracing::warn!( + "{:?}", + (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) + ); + // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, ev.origin_server_ts().clone(), ev.event_id().cloned()) + (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) }) } @@ -371,8 +411,8 @@ impl StateResolution { // TODO make the BTreeSet conversion cleaner ?? let mut outdegree_map: BTreeMap> = graph - .into_iter() - .map(|(k, v)| (k.clone(), v.into_iter().cloned().collect())) + .iter() + .map(|(k, v)| (k.clone(), v.iter().cloned().collect())) .collect(); let mut reverse_graph = BTreeMap::new(); @@ -432,7 +472,7 @@ impl StateResolution { let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? - for aid in store.get_event(event_id).unwrap().auth_event_ids() { + for aid in store.get_event(event_id).unwrap().auth_events() { if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); @@ -442,7 +482,7 @@ impl StateResolution { } if pl.is_none() { - for aid in store.get_event(event_id).unwrap().auth_event_ids() { + for aid in store.get_event(event_id).unwrap().auth_events() { if let Ok(aev) = store.get_event(&aid) { if aev.is_type_and_key(EventType::RoomCreate, "") { if let Ok(content) = aev @@ -487,16 +527,25 @@ impl StateResolution { unconflicted_state: &StateMap, _event_map: &EventMap, // TODO use event_map over store ?? store: &dyn StateStore, - ) -> Result, String> { + ) -> Result> { tracing::info!("starting iterative auth check"); + tracing::debug!( + "{:?}", + power_events + .iter() + .map(ToString::to_string) + .collect::>() + ); + let mut resolved_state = unconflicted_state.clone(); for (idx, event_id) in power_events.iter().enumerate() { + tracing::warn!("POWER EVENTS {}", event_id.as_str()); let event = store.get_event(event_id).unwrap(); let mut auth_events = BTreeMap::new(); - for aid in event.auth_event_ids() { + for aid in event.auth_events() { if let Ok(ev) = store.get_event(&aid) { // TODO what to do when no state_key is found ?? // TODO check "rejected_reason", I'm guessing this is redacted_because for ruma ?? @@ -508,9 +557,8 @@ impl StateResolution { 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) { + // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); } } @@ -518,7 +566,8 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().unwrap().to_string()); if event_auth::auth_check(room_version, &event, auth_events, false) - .ok_or("Auth check failed due to deserialization most likely".to_string())? + .ok_or("Auth check failed due to deserialization most likely".to_string()) + .map_err(Error::TempString)? { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key().unwrap()), event_id.clone()); @@ -567,7 +616,7 @@ impl StateResolution { // We don't need the actual pl_ev here since we delegate to the store let event = store.get_event(&p).unwrap(); - let auth_events = event.auth_event_ids(); + let auth_events = event.auth_events(); pl = None; for aid in auth_events { let ev = store.get_event(&aid).unwrap(); @@ -635,7 +684,7 @@ impl StateResolution { } } - let auth_events = sort_ev.auth_event_ids(); + let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { let aev = store.get_event(&aid).unwrap(); @@ -664,7 +713,7 @@ impl StateResolution { graph.entry(eid.clone()).or_insert(vec![]); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in store.get_event(&eid).unwrap().auth_event_ids() { + for aid in store.get_event(&eid).unwrap().auth_events() { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { state.push(aid.clone()); diff --git a/src/state_event.rs b/src/state_event.rs index 897834f6..f2f8cd95 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -173,29 +173,29 @@ impl StateEvent { pub fn prev_event_ids(&self) -> Vec { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().cloned().collect(), + Pdu::RoomV1Pdu(ev) => ev.prev_events.to_vec(), 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(), + PduStub::RoomV3PduStub(ev) => ev.prev_events.to_vec(), }, } } - pub fn auth_event_ids(&self) -> Vec { + pub fn auth_events(&self) -> Vec { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().cloned().collect(), - Pdu::RoomV3Pdu(ev) => ev.auth_events.clone(), + Pdu::RoomV1Pdu(ev) => ev.auth_events.to_vec(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, Self::Sync(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.auth_events.iter().map(|(id, _)| id).cloned().collect() } - PduStub::RoomV3PduStub(ev) => ev.auth_events.clone(), + PduStub::RoomV3PduStub(ev) => ev.auth_events.to_vec(), }, } } diff --git a/tests/auth_ids.rs b/tests/auth_ids.rs new file mode 100644 index 00000000..45bbd1da --- /dev/null +++ b/tests/auth_ids.rs @@ -0,0 +1,741 @@ +#![allow(clippy::or_fun_call, clippy::expect_fun_call)] + +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + convert::TryFrom, + sync::Once, + time::UNIX_EPOCH, +}; + +use ruma::{ + events::{ + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +use serde_json::{json, Value as JsonValue}; +use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; +use tracing_subscriber as tracer; + +static LOGGER: Once = Once::new(); + +static mut SERVER_TIMESTAMP: i32 = 0; + +fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { + // to activate logging use `RUST_LOG=debug cargo t one_test_only` + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + let mut resolver = StateResolution::default(); + + let store = TestStore(RefCell::new( + INITIAL_EVENTS() + .values() + .chain(events) + .map(|ev| (ev.event_id().unwrap().clone(), ev.clone())) + .collect(), + )); + + // This will be lexi_topo_sorted for resolution + let mut graph = BTreeMap::new(); + // this is the same as in `resolve` event_id -> StateEvent + let mut fake_event_map = BTreeMap::new(); + + // create the DB of events that led up to this point + // TODO maybe clean up some of these clones it is just tests but... + for ev in INITIAL_EVENTS().values().chain(events) { + graph.insert(ev.event_id().unwrap().clone(), vec![]); + fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); + } + + for pair in INITIAL_EDGES().windows(2) { + if let [a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + + for edge_list in edges { + for pair in edge_list.windows(2) { + if let [a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + } + + // event_id -> StateEvent + let mut event_map: BTreeMap = BTreeMap::new(); + // event_id -> StateMap + let mut state_at_event: BTreeMap> = BTreeMap::new(); + + // resolve the current state and add it to the state_at_event map then continue + // on in "time" + for node in + resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) + { + let fake_event = fake_event_map.get(&node).unwrap(); + let event_id = fake_event.event_id().unwrap(); + + let prev_events = graph.get(&node).unwrap(); + + let state_before: StateMap = if prev_events.is_empty() { + BTreeMap::new() + } else if prev_events.len() == 1 { + state_at_event.get(&prev_events[0]).unwrap().clone() + } else { + let state_sets = prev_events + .iter() + .filter_map(|k| state_at_event.get(k)) + .cloned() + .collect::>(); + + tracing::info!( + "{:#?}", + state_sets + .iter() + .map(|map| map + .iter() + .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .collect::>()) + .collect::>() + ); + + let resolved = resolver.resolve( + &room_id(), + &RoomVersionId::version_1(), + &state_sets, + Some(event_map.clone()), + &store, + ); + match resolved { + Ok(ResolutionResult::Resolved(state)) => state, + Ok(ResolutionResult::Conflicted(state)) => panic!( + "conflicted: {:?}", + state + .iter() + .map(|map| map + .iter() + .map(|(key, id)| (key, id.to_string())) + .collect::>()) + .collect::>() + ), + Err(e) => panic!("resolution for {} failed: {}", node, e), + } + }; + + let mut state_after = state_before.clone(); + + if fake_event.state_key().is_some() { + let ty = fake_event.kind().clone(); + // we know there is a state_key unwrap OK + let key = fake_event.state_key().unwrap().clone(); + state_after.insert((ty, key), event_id.clone()); + } + + let auth_types = state_res::auth_types_for_event(fake_event); + + let mut auth_events = vec![]; + for key in auth_types { + if state_before.contains_key(&key) { + auth_events.push(state_before[&key].clone()) + } + } + + // TODO The event is just remade, adding the auth_events and prev_events here + // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better + let e = fake_event; + let ev_id = e.event_id().unwrap(); + let event = to_pdu_event( + &e.event_id().unwrap().to_string(), + e.sender().clone(), + e.kind(), + e.state_key().as_deref(), + e.content().clone(), + &auth_events, + prev_events, + ); + // we have to update our store, an actual user of this lib would + // be giving us state from a DB. + // + // TODO + // TODO we need to convert the `StateResolution::resolve` to use the event_map + // because the user of this crate cannot update their DB's state. + *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); + + state_at_event.insert(node, state_after); + event_map.insert(event_id.clone(), event); + } + + let mut expected_state = BTreeMap::new(); + for node in expected_state_ids { + let ev = event_map.get(&node).expect(&format!( + "{} not found in {:?}", + node.to_string(), + event_map + .keys() + .map(ToString::to_string) + .collect::>(), + )); + + let key = (ev.kind(), ev.state_key().unwrap()); + + expected_state.insert(key, node); + } + + let start_state = state_at_event.get(&event_id("$START:foo")).unwrap(); + + let end_state = state_at_event + .get(&event_id("$END:foo")) + .unwrap() + .iter() + .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>(); + + assert_eq!(expected_state, end_state); +} +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_events(&self, events: &[EventId]) -> Result, String> { + Ok(self + .0 + .borrow() + .iter() + .filter(|e| events.contains(e.0)) + .map(|(_, s)| s) + .cloned() + .collect()) + } + + fn get_event(&self, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } + + fn auth_event_ids( + &self, + room_id: &RoomId, + event_ids: &[EventId], + ) -> Result, String> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(&ev_id).unwrap(); + stack.extend(event.auth_events()); + } + + Ok(result) + } + + fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: Vec>, + ) -> Result, String> { + use itertools::Itertools; + + let mut chains = vec![]; + for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } +} + +fn event_id(id: &str) -> EventId { + if id.contains('$') { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} +fn ella() -> UserId { + UserId::try_from("@ella:foo").unwrap() +} +fn zara() -> UserId { + UserId::try_from("@zara:foo").unwrap() +} + +fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .collect::>(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .collect::>(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + // this is always called so we can init the logger here + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + to_pdu_event::( + "START", + charlie(), + EventType::RoomMessage, + None, + json!({}), + &[], + &[], + ), + to_pdu_event::( + "END", + charlie(), + EventType::RoomMessage, + None, + json!({}), + &[], + &[], + ), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} + +#[allow(non_snake_case)] +fn INITIAL_EDGES() -> Vec { + vec!["START", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"] + .into_iter() + .map(event_id) + .collect::>() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn BAN_STATE_SET() -> BTreeMap { + vec![ + to_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], // auth_events + &["START"], // prev_events + ), + to_pdu_event( + "PB", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], + &["END"], + ), + to_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_ban(), + &["CREATE", "IMA", "PB"], + &["PA"], + ), + to_pdu_event( + "IME", + ella(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_join(), + &["CREATE", "IJR", "PA"], + &["MB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} + +#[test] +fn ban_with_auth_chains() { + let ban = BAN_STATE_SET(); + + let edges = vec![vec!["END", "MB", "PA", "START"], vec!["END", "IME", "MB"]] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec!["PA", "MB"] + .into_iter() + .map(event_id) + .collect::>(); + + do_check( + &ban.values().cloned().collect::>(), + edges, + expected_state_ids, + ); +} + +#[test] +fn base_with_auth_chains() { + let mut resolver = StateResolution::default(); + + let store = TestStore(RefCell::new(INITIAL_EVENTS())); + + let resolved: BTreeMap<_, EventId> = + match resolver.resolve(&room_id(), &RoomVersionId::version_2(), &[], None, &store) { + Ok(ResolutionResult::Resolved(state)) => state, + Err(e) => panic!("{}", e), + _ => panic!("conflicted state left"), + }; + + let resolved = resolved + .values() + .cloned() + .chain( + INITIAL_EVENTS() + .values() + .map(|e| e.event_id().unwrap().clone()), + ) + .collect::>(); + + let expected = vec![ + "$CREATE:foo", + "$IJR:foo", + "$IPOWER:foo", + "$IMA:foo", + "$IMB:foo", + "$IMC:foo", + "START", + "END", + ]; + for id in expected.iter().map(|i| event_id(i)) { + // make sure our resolved events are equall to the expected list + assert!(resolved.iter().any(|eid| eid == &id), "{}", id) + } + assert_eq!(expected.len(), resolved.len()) +} + +#[test] +fn ban_with_auth_chains2() { + let mut resolver = StateResolution::default(); + + let init = INITIAL_EVENTS(); + let ban = BAN_STATE_SET(); + + let mut inner = init.clone(); + inner.extend(ban); + let store = TestStore(RefCell::new(inner.clone())); + + let state_set_a = [ + inner.get(&event_id("CREATE")).unwrap(), + inner.get(&event_id("IJR")).unwrap(), + inner.get(&event_id("IMA")).unwrap(), + inner.get(&event_id("IMB")).unwrap(), + inner.get(&event_id("IMC")).unwrap(), + inner.get(&event_id("MB")).unwrap(), + inner.get(&event_id("PA")).unwrap(), + ] + .iter() + .map(|ev| { + ( + (ev.kind(), ev.state_key().unwrap()), + ev.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let state_set_b = [ + inner.get(&event_id("CREATE")).unwrap(), + inner.get(&event_id("IJR")).unwrap(), + inner.get(&event_id("IMA")).unwrap(), + inner.get(&event_id("IMB")).unwrap(), + inner.get(&event_id("IMC")).unwrap(), + inner.get(&event_id("IME")).unwrap(), + inner.get(&event_id("PA")).unwrap(), + ] + .iter() + .map(|ev| { + ( + (ev.kind(), ev.state_key().unwrap()), + ev.event_id().unwrap().clone(), + ) + }) + .collect::>(); + + let resolved: BTreeMap<_, EventId> = match resolver.resolve( + &room_id(), + &RoomVersionId::version_2(), + &[state_set_a, state_set_b], + None, + &store, + ) { + Ok(ResolutionResult::Resolved(state)) => state, + Err(e) => panic!("{}", e), + _ => panic!("conflicted state left"), + }; + + tracing::debug!( + "{:#?}", + resolved + .iter() + .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .collect::>() + ); + + let expected = vec![ + "$CREATE:foo", + "$IJR:foo", + "$PA:foo", + "$IMA:foo", + "$IMB:foo", + "$IMC:foo", + "$MB:foo", + ]; + + for id in expected.iter().map(|i| event_id(i)) { + // make sure our resolved events are equall to the expected list + assert!( + resolved.values().any(|eid| eid == &id) || init.contains_key(&id), + "{}", + id + ) + } + assert_eq!(expected.len(), resolved.len()) +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn JOIN_RULE() -> BTreeMap { + vec![ + to_pdu_event( + "JR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({"join_rule": "invite"}), + &["CREATE", "IMA", "IPOWER"], + &["START"], + ), + to_pdu_event( + "IMZ", + zara(), + EventType::RoomPowerLevels, + Some(zara().as_str()), + member_content_join(), + &["CREATE", "JR", "IPOWER"], + &["START"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .collect() +} + +#[test] +fn join_rule_with_auth_chain() { + let join_rule = JOIN_RULE(); + + let edges = vec![vec!["END", "JR", "START"], vec!["END", "IMZ", "START"]] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec!["JR"].into_iter().map(event_id).collect::>(); + + do_check( + &join_rule.values().cloned().collect::>(), + edges, + expected_state_ids, + ); +} diff --git a/tests/state_res.rs b/tests/state_res.rs index e36f30ac..06bfbf5c 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,3 +1,5 @@ +#![allow(clippy::or_fun_call, clippy::expect_fun_call)] + use std::{ cell::RefCell, collections::{BTreeMap, BTreeSet}, @@ -27,7 +29,7 @@ static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; fn event_id(id: &str) -> EventId { - if id.contains("$") { + if id.contains('$') { return EventId::try_from(id).unwrap(); } EventId::try_from(format!("${}:foo", id)).unwrap() @@ -92,7 +94,7 @@ where SERVER_TIMESTAMP += 1; ts }; - let id = if id.contains("$") { + let id = if id.contains('$') { id.to_string() } else { format!("${}:foo", id) @@ -100,33 +102,13 @@ where let auth_events = auth_events .iter() .map(AsRef::as_ref) - .map(|s| { - EventId::try_from( - if s.contains("$") { - s.to_owned() - } else { - format!("${}:foo", s) - } - .as_str(), - ) - }) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) - .map(|s| { - EventId::try_from( - if s.contains("$") { - s.to_owned() - } else { - format!("${}:foo", s) - } - .as_str(), - ) - }) - .collect::, _>>() - .unwrap(); + .map(event_id) + .collect::>(); let json = if let Some(state_key) = state_key { json!({ @@ -176,7 +158,7 @@ fn to_init_pdu_event( SERVER_TIMESTAMP += 1; ts }; - let id = if id.contains("$") { + let id = if id.contains('$') { id.to_string() } else { format!("${}:foo", id) @@ -319,14 +301,14 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } for pair in INITIAL_EDGES().windows(2) { - if let &[a, b] = &pair { + if let [a, b] = &pair { graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); } } for edge_list in edges { for pair in edge_list.windows(2) { - if let &[a, b] = &pair { + if let [a, b] = &pair { graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); } } @@ -338,10 +320,9 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: let mut state_at_event: BTreeMap> = BTreeMap::new(); // resolve the current state and add it to the state_at_event map then continue - // on in "time"? - for node in resolver - // TODO is this `key_fn` return correct ?? - .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) + // on in "time" + for node in + resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) { let fake_event = fake_event_map.get(&node).unwrap(); let event_id = fake_event.event_id().unwrap(); @@ -359,6 +340,17 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .cloned() .collect::>(); + tracing::warn!( + "{:#?}", + state_sets + .iter() + .map(|map| map + .iter() + .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .collect::>()) + .collect::>() + ); + let resolved = resolver.resolve( &room_id(), &RoomVersionId::version_1(), @@ -791,7 +783,8 @@ impl StateStore for TestStore { result.push(ev_id.clone()); let event = self.get_event(&ev_id).unwrap(); - stack.extend(event.auth_event_ids()); + + stack.extend(event.auth_events()); } Ok(result) @@ -902,7 +895,7 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id().unwrap().clone()], + &[cre, join_rules.event_id().unwrap().clone()], &[join_rules.event_id().unwrap().clone()], ); self.0 @@ -913,7 +906,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) @@ -923,7 +916,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) @@ -939,7 +932,7 @@ impl TestStore { .iter() .map(|e| { ( - (e.kind(), e.state_key().unwrap().clone()), + (e.kind(), e.state_key().unwrap()), e.event_id().unwrap().clone(), ) }) From 4990dac5fec912bd00529aee5fe796bfee6d8a37 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 27 Jul 2020 16:47:55 -0400 Subject: [PATCH 017/130] Move all event access to _get_event method We now use the event_map when possible, only accessing the state store when event_map fails. A -4.8578% increase in perf was observed. --- src/lib.rs | 108 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e5f9da55..bae650c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,6 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], - // TODO remove or make this mut so we aren't cloning the whole thing event_map: Option>, store: &dyn StateStore, // TODO actual error handling (`thiserror`??) @@ -110,7 +109,7 @@ impl StateResolution { tracing::info!("computing {} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks - let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, &event_map, store)?; + let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, store)?; tracing::debug!("auth diff size {}", auth_diff.len()); @@ -142,6 +141,7 @@ impl StateResolution { .into_iter() .flat_map(|ev| Some((ev.event_id()?.clone(), ev))), ); + // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -159,7 +159,6 @@ impl StateResolution { } } - // TODO make sure each conflicting event is in event_map?? // synapse says `full_set = {eid for eid in full_conflicted_set if eid in event_map}` // // don't honor events we cannot "verify" @@ -176,7 +175,7 @@ impl StateResolution { let mut sorted_power_levels = self.reverse_topological_power_sort( room_id, &power_events, - &event_map, // TODO use event_map + &mut event_map, // TODO use event_map store, &all_conflicted, ); @@ -195,7 +194,7 @@ impl StateResolution { room_version, &sorted_power_levels, &clean, - &event_map, + &mut event_map, store, )?; @@ -231,8 +230,13 @@ impl StateResolution { tracing::debug!("PL {:?}", power_event); - let sorted_left_events = - self.mainline_sort(room_id, &events_to_resolve, power_event, &event_map, store); + let sorted_left_events = self.mainline_sort( + room_id, + &events_to_resolve, + power_event, + &mut event_map, + store, + ); tracing::debug!( "SORTED LEFT {:?}", @@ -247,7 +251,7 @@ impl StateResolution { room_version, &sorted_left_events, &resolved, - &event_map, + &mut event_map, store, )?; @@ -318,7 +322,6 @@ impl StateResolution { &mut self, room_id: &RoomId, state_sets: &[StateMap], - _event_map: &EventMap, store: &dyn StateStore, ) -> Result> { use itertools::Itertools; @@ -341,7 +344,7 @@ impl StateResolution { &mut self, room_id: &RoomId, power_events: &[EventId], - event_map: &EventMap, + event_map: &mut EventMap, store: &dyn StateStore, auth_diff: &[EventId], ) -> Vec { @@ -349,7 +352,9 @@ impl StateResolution { 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); + self.add_event_and_auth_chain_to_graph( + room_id, &mut graph, event_id, event_map, 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. @@ -461,19 +466,20 @@ impl StateResolution { } fn get_power_level_for_sender( - &self, - _room_id: &RoomId, + &mut self, + room_id: &RoomId, event_id: &EventId, - event_map: &EventMap, // TODO use event_map over store ?? + event_map: &mut EventMap, store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event senders ({}) power level", event_id.to_string()); - let event = event_map.get(event_id); + let event = self._get_event(room_id, event_id, event_map, store); let mut pl = None; + // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? - for aid in store.get_event(event_id).unwrap().auth_events() { - if let Ok(aev) = store.get_event(&aid) { + for aid in event.as_ref().unwrap().auth_events() { + if let Some(aev) = self._get_event(room_id, &aid, event_map, store) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -521,11 +527,11 @@ impl StateResolution { fn iterative_auth_check( &mut self, - _room_id: &RoomId, + room_id: &RoomId, room_version: &RoomVersionId, power_events: &[EventId], unconflicted_state: &StateMap, - _event_map: &EventMap, // TODO use event_map over store ?? + event_map: &mut EventMap, // TODO use event_map over store ?? store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -542,11 +548,13 @@ impl StateResolution { for (idx, event_id) in power_events.iter().enumerate() { tracing::warn!("POWER EVENTS {}", event_id.as_str()); - let event = store.get_event(event_id).unwrap(); + let event = self + ._get_event(room_id, event_id, event_map, store) + .unwrap(); let mut auth_events = BTreeMap::new(); for aid in event.auth_events() { - if let Ok(ev) = store.get_event(&aid) { + if let Some(ev) = self._get_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? // TODO check "rejected_reason", I'm guessing this is redacted_because for ruma ?? auth_events.insert((ev.kind(), ev.state_key().unwrap()), ev); @@ -557,7 +565,7 @@ impl StateResolution { for key in event_auth::auth_types_for_event(&event) { if let Some(ev_id) = resolved_state.get(&key) { - if let Ok(event) = store.get_event(ev_id) { + if let Some(event) = self._get_event(room_id, ev_id, event_map, store) { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); } @@ -590,12 +598,15 @@ impl StateResolution { /// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using /// the `resolved_power_level`. + /// + /// NOTE we rely on the `event_map` beign full at this point. + /// TODO is this ok? fn mainline_sort( &mut self, - _room_id: &RoomId, + room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, - event_map: &EventMap, + event_map: &mut EventMap, store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of remaining events"); @@ -614,12 +625,11 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); - // We don't need the actual pl_ev here since we delegate to the store - let event = store.get_event(&p).unwrap(); + let event = self._get_event(room_id, &p, event_map, store).unwrap(); let auth_events = event.auth_events(); pl = None; for aid in auth_events { - let ev = store.get_event(&aid).unwrap(); + let ev = self._get_event(room_id, &aid, event_map, store).unwrap(); if ev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; @@ -643,12 +653,16 @@ impl StateResolution { let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - let depth = self.get_mainline_depth(store.get_event(ev_id).ok(), &mainline_map, store); + let event = self._get_event(room_id, ev_id, event_map, store); + let depth = self.get_mainline_depth(room_id, event, &mainline_map, event_map, store); order_map.insert( ev_id, ( depth, - event_map.get(ev_id).map(|ev| ev.origin_server_ts()), + event_map + .get(ev_id) + .map(|ev| ev.origin_server_ts()) + .cloned(), ev_id, // TODO should this be a &str to sort lexically?? ), ); @@ -667,10 +681,13 @@ impl StateResolution { sort_event_ids } + // TODO make `event` not clone every loop fn get_mainline_depth( &mut self, + room_id: &RoomId, mut event: Option, mainline_map: &EventMap, + event_map: &mut EventMap, store: &dyn StateStore, ) -> usize { while let Some(sort_ev) = event { @@ -687,9 +704,9 @@ impl StateResolution { let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { - let aev = store.get_event(&aid).unwrap(); + let aev = self._get_event(room_id, &aid, event_map, store).unwrap(); if aev.is_type_and_key(EventType::RoomPowerLevels, "") { - event = Some(aev); + event = Some(aev.clone()); break; } } @@ -699,10 +716,11 @@ impl StateResolution { } fn add_event_and_auth_chain_to_graph( - &self, - _room_id: &RoomId, + &mut self, + room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, + event_map: &mut EventMap, store: &dyn StateStore, auth_diff: &[EventId], ) { @@ -713,7 +731,11 @@ impl StateResolution { graph.entry(eid.clone()).or_insert(vec![]); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in store.get_event(&eid).unwrap().auth_events() { + for aid in self + ._get_event(room_id, &eid, event_map, store) + .unwrap() + .auth_events() + { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { state.push(aid.clone()); @@ -725,6 +747,24 @@ impl StateResolution { } } } + + /// TODO update self if we go that route just as event_map will be updated + fn _get_event( + &mut self, + _room_id: &RoomId, + ev_id: &EventId, + event_map: &mut EventMap, + store: &dyn StateStore, + ) -> Option { + // TODO can we cut down on the clones? + if !event_map.contains_key(ev_id) { + let event = store.get_event(ev_id).ok()?; + event_map.insert(ev_id.clone(), event.clone()); + Some(event) + } else { + event_map.get(ev_id).cloned() + } + } } pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool { From 77cbcc7ee2b0e4a27f3b54bc72213317b08bfa1e Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 27 Jul 2020 17:10:06 -0400 Subject: [PATCH 018/130] Clean up logging calls --- src/event_auth.rs | 70 +++++++++++++++++++++------------------------- src/lib.rs | 48 ++++++++----------------------- tests/state_res.rs | 2 +- 3 files changed, 45 insertions(+), 75 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 8fad3fdd..c5a630eb 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -68,12 +68,15 @@ pub fn auth_check( auth_events: StateMap, do_sig_check: bool, ) -> Option { - tracing::info!("auth_check beginning"); + tracing::info!( + "auth_check beginning for {}", + event.event_id().unwrap().as_str() + ); // don't let power from other rooms be used for auth_event in auth_events.values() { if auth_event.room_id() != event.room_id() { - tracing::info!("found auth event that did not match event's room_id"); + tracing::warn!("found auth event that did not match event's room_id"); return Some(false); } } @@ -91,7 +94,7 @@ pub fn auth_check( }; if event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { - tracing::info!("event not signed by sender's server"); + tracing::warn!("event not signed by sender's server"); return Some(false); } } @@ -108,7 +111,7 @@ pub fn auth_check( // domain of room_id must match domain of sender. if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { - tracing::info!("creation events server does not match sender"); + tracing::warn!("creation events server does not match sender"); return Some(false); // creation events room id does not match senders } @@ -124,6 +127,7 @@ pub fn auth_check( ) .is_err() { + tracing::warn!("invalid room version found in m.room.create event"); return Some(false); } @@ -157,13 +161,16 @@ pub fn auth_check( tracing::info!("starting m.room.aliases check"); // TODO && room_version "special case aliases auth" ?? if event.state_key().is_none() { + tracing::warn!("no state_key field found for event"); return Some(false); // must have state_key } if event.state_key().unwrap().is_empty() { + tracing::warn!("state_key must be non-empty"); return Some(false); // and be non-empty state_key (point to a user_id) } if event.state_key() != Some(event.sender().to_string()) { + tracing::warn!("no state_key field found for event"); return Some(false); } @@ -272,7 +279,7 @@ fn is_membership_change_allowed( if event.room_id().unwrap().server_name() != target_user_id.server_name() && !can_federate(auth_events) { - tracing::info!("server cannot federate"); + tracing::warn!("server cannot federate"); return Some(false); } @@ -325,23 +332,11 @@ fn is_membership_change_allowed( if membership == MembershipState::Invite && content.third_party_invite.is_some() { // TODO this is unimpled if !verify_third_party_invite(event, auth_events) { - tracing::info!( - "{} was not invited to this room", - event - .event_id() - .map(ToString::to_string) - .unwrap_or("Unknow".into()) - ); + tracing::warn!("not invited to this room",); return Some(false); } if target_banned { - tracing::info!( - "{} is banned", - event - .event_id() - .map(ToString::to_string) - .unwrap_or("Unknow".into()) - ); + tracing::warn!("banned from this room",); return Some(false); } tracing::info!("invite succeded"); @@ -353,15 +348,14 @@ fn is_membership_change_allowed( && membership == MembershipState::Leave && &target_user_id == event.sender() { - tracing::info!("join event succeded"); + tracing::warn!("join event succeded"); return Some(true); } if !caller_in_room { - tracing::info!( - "{} is not in this room {:?}", - event.sender(), - event.room_id() + tracing::warn!( + "user is not in this room {}", + event.room_id().unwrap().as_str(), ); return Some(false); // caller is not joined } @@ -369,10 +363,10 @@ fn is_membership_change_allowed( if membership == MembershipState::Invite { if target_banned { - tracing::info!("target has been banned"); + tracing::warn!("target has been banned"); return Some(false); } else if target_in_room { - tracing::info!("already in room"); + tracing::warn!("already in room"); return Some(false); // already in room } else { let invite_level = get_named_level(auth_events, "invite", 0); @@ -382,21 +376,21 @@ fn is_membership_change_allowed( } } else if membership == MembershipState::Join { if event.sender() != &target_user_id { - tracing::info!("cannot force another user to join"); + tracing::warn!("cannot force another user to join"); return Some(false); // cannot force another user to join } else if target_banned { - tracing::info!("cannot join when banned"); + tracing::warn!("cannot join when banned"); return Some(false); // cannot joined when banned } else if join_rule == JoinRule::Public { tracing::info!("join rule public") // pass } else if join_rule == JoinRule::Invite { if !caller_in_room && !caller_invited { - tracing::info!("user has not been invited to this room"); + tracing::warn!("user has not been invited to this room"); return Some(false); // you are not invited to this room } } else { - tracing::info!("the join rule is Private or yet to be spec'ed by Matrix"); + tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix"); // synapse has 2 TODO's may_join list and private rooms // the join_rule is Private or Knock which means it is not yet spec'ed @@ -404,13 +398,13 @@ fn is_membership_change_allowed( } } else if membership == MembershipState::Leave { if target_banned && user_level < ban_level { - tracing::info!("not enough power to unban"); + tracing::warn!("not enough power to unban"); 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 { - tracing::info!("not enough power to kick user"); + tracing::warn!("not enough power to kick user"); return Some(false); // you do not have the power to kick user } } @@ -423,7 +417,7 @@ fn is_membership_change_allowed( target_level ); if user_level < ban_level || user_level <= target_level { - tracing::info!("not enough power to ban"); + tracing::warn!("not enough power to ban"); return Some(false); } } else { @@ -538,7 +532,7 @@ fn check_power_levels( 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 { - tracing::info!("m.room.power_level cannot add ops > than own"); + tracing::warn!("m.room.power_level cannot add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -557,14 +551,14 @@ fn check_power_levels( continue; } if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { - tracing::info!("m.room.power_level cannot remove ops == to own"); + tracing::warn!("m.room.power_level cannot remove ops == to own"); 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 { - tracing::info!("m.room.power_level failed to add ops > than own"); + tracing::warn!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -580,7 +574,7 @@ fn check_power_levels( 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 { - tracing::info!("m.room.power_level failed to add ops > than own"); + tracing::warn!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -602,7 +596,7 @@ fn check_power_levels( let new_level_too_big = new_lvl > user_level; if old_level_too_big || new_level_too_big { - tracing::info!("cannot add ops > than own"); + tracing::warn!("cannot add ops > than own"); return Some(false); } } diff --git a/src/lib.rs b/src/lib.rs index bae650c9..0849694f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,34 +79,15 @@ impl StateResolution { }; // split non-conflicting and conflicting state let (clean, conflicting) = self.separate(&state_sets); - tracing::debug!( - "CLEAN {:#?}", - clean - .iter() - .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) - .collect::>() - ); - tracing::debug!( - "CONFLICT {:#?}", - conflicting - .iter() - .map(|((ty, key), ids)| format!( - "(({} `{}`), {:?})", - ty, - key, - ids.iter().map(ToString::to_string).collect::>() - )) - .collect::>() - ); tracing::info!("non conflicting {:?}", clean.len()); if conflicting.is_empty() { - tracing::warn!("no conflicting state found"); + tracing::info!("no conflicting state found"); return Ok(ResolutionResult::Resolved(clean)); } - tracing::info!("computing {} conflicting events", conflicting.len()); + tracing::info!("{} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, store)?; @@ -167,7 +148,7 @@ impl StateResolution { // 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)) + .filter(|id| is_power_event(id, &event_map)) .cloned() .collect::>(); @@ -306,7 +287,6 @@ impl StateResolution { panic!() } } else { - tracing::warn!("{:?}", key); conflicted_state.insert( key.clone(), event_ids.into_iter().flatten().cloned().collect::>(), @@ -383,7 +363,7 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::warn!( + tracing::debug!( "{:?}", (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) ); @@ -547,7 +527,6 @@ impl StateResolution { let mut resolved_state = unconflicted_state.clone(); for (idx, event_id) in power_events.iter().enumerate() { - tracing::warn!("POWER EVENTS {}", event_id.as_str()); let event = self ._get_event(room_id, event_id, event_map, store) .unwrap(); @@ -556,7 +535,7 @@ impl StateResolution { for aid in event.auth_events() { if let Some(ev) = self._get_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? - // TODO check "rejected_reason", I'm guessing this is redacted_because for ruma ?? + // TODO synapse check "rejected_reason", I'm guessing this is redacted_because for ruma ?? auth_events.insert((ev.kind(), ev.state_key().unwrap()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); @@ -580,7 +559,7 @@ impl StateResolution { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key().unwrap()), event_id.clone()); } else { - // TODO synapse passes here on AuthError ?? + // synapse passes here on AuthError. We do not add this event to resolved_state. tracing::warn!( "event {} failed the authentication check", event_id.to_string() @@ -610,11 +589,8 @@ impl StateResolution { store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of remaining events"); - // tracing::debug!( - // "{:?}", - // to_sort.iter().map(ToString::to_string).collect::>() - // ); - // There can be no EventId's to sort, bail. + + // There are no EventId's to sort, bail. if to_sort.is_empty() { return vec![]; } @@ -692,7 +668,7 @@ impl StateResolution { ) -> usize { while let Some(sort_ev) = event { tracing::debug!( - "mainline EVENT ID {}", + "mainline event_id {}", sort_ev.event_id().unwrap().to_string() ); if let Some(id) = sort_ev.event_id() { @@ -767,9 +743,9 @@ impl StateResolution { } } -pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool { - match store.get_event(event_id) { - Ok(state) => state.is_power_event(), +pub fn is_power_event(event_id: &EventId, event_map: &EventMap) -> bool { + match event_map.get(event_id) { + Some(state) => state.is_power_event(), _ => false, // TODO this shouldn't eat errors? } } diff --git a/tests/state_res.rs b/tests/state_res.rs index 06bfbf5c..ba4393f8 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -340,7 +340,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .cloned() .collect::>(); - tracing::warn!( + tracing::debug!( "{:#?}", state_sets .iter() From 8a9f15e01f11142d938939934dc891f928632095 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 28 Jul 2020 07:07:38 -0400 Subject: [PATCH 019/130] House keeping, remove &mut self in all methods Improved perf -7.8220% --- Cargo.toml | 10 ++++- benches/event_auth_bench.rs | 0 .../{state_bench.rs => state_res_bench.rs} | 6 +-- src/lib.rs | 42 ++++++------------- tests/auth_ids.rs | 6 +-- tests/state_res.rs | 6 +-- 6 files changed, 30 insertions(+), 40 deletions(-) create mode 100644 benches/event_auth_bench.rs rename benches/{state_bench.rs => state_res_bench.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index 53d0c68e..fc28c60d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,13 @@ name = "state-res" version = "0.1.0" authors = ["Devin R "] edition = "2018" +categories = ["api-bindings", "web-programming"] +description = "An abstraction for Matrix state resolution." +homepage = "https://www.ruma.io/" +keywords = ["matrix", "chat", "state resolution", "ruma"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/ruma/state-res" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -19,7 +26,6 @@ tracing-subscriber = "0.2.8" [dependencies.ruma] git = "https://github.com/DevinR528/ruma" branch = "pdu-deserialize" -# path = "../__forks__/ruma/ruma" features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] @@ -27,5 +33,5 @@ lazy_static = "1.4.0" criterion = "0.3.3" [[bench]] -name = "state_bench" +name = "state_res_bench" harness = false \ No newline at end of file diff --git a/benches/event_auth_bench.rs b/benches/event_auth_bench.rs new file mode 100644 index 00000000..e69de29b diff --git a/benches/state_bench.rs b/benches/state_res_bench.rs similarity index 98% rename from benches/state_bench.rs rename to benches/state_res_bench.rs index c4fa0e40..555677d6 100644 --- a/benches/state_bench.rs +++ b/benches/state_res_bench.rs @@ -37,7 +37,7 @@ fn lexico_topo_sort(c: &mut Criterion) { event_id("p") => vec![event_id("o")], }; b.iter(|| { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let _ = resolver .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))); @@ -47,7 +47,7 @@ fn lexico_topo_sort(c: &mut Criterion) { fn resolution_shallow_auth_chain(c: &mut Criterion) { c.bench_function("resolve state of 5 events one fork", |b| { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let store = TestStore(RefCell::new(btreemap! {})); @@ -72,7 +72,7 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { fn resolve_deeper_event_set(c: &mut Criterion) { c.bench_function("resolve state of 10 events 3 conflicting", |b| { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let init = INITIAL_EVENTS(); let ban = BAN_STATE_SET(); diff --git a/src/lib.rs b/src/lib.rs index 0849694f..a0867678 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,6 @@ use ruma::{ events::EventType, identifiers::{EventId, RoomId, RoomVersionId}, }; -use serde::{Deserialize, Serialize}; mod error; mod event_auth; @@ -40,29 +39,14 @@ pub type StateMap = BTreeMap<(EventType, String), T>; /// A mapping of `EventId` to `T`, usually a `StateEvent`. pub type EventMap = BTreeMap; -#[derive(Debug, Default, Deserialize, Serialize)] // TODO make the ser/de impls useful -pub struct StateResolution { - // TODO remove pub after initial testing - /// The set of resolved events over time. - pub resolved_events: Vec, - /// The resolved state, kept to have easy access to the last resolved - /// layer of state. - pub state: BTreeMap>, - /// The graph of authenticated events, kept to find the most recent auth event - /// in a chain for incoming state sets. - pub auth_graph: BTreeMap>>, - /// The last known point in the state graph. - pub most_recent_resolved: Option<(EventType, String)>, - - // fields for temp storage during resolution - pub conflicting_events: Vec, -} +#[derive(Default)] +pub struct StateResolution; impl StateResolution { /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. pub fn resolve( - &mut self, + &self, room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], @@ -247,7 +231,7 @@ impl StateResolution { /// /// The tuple looks like `(unconflicted, conflicted)`. pub fn separate( - &mut self, + &self, state_sets: &[StateMap], ) -> (StateMap, StateMap>) { use itertools::Itertools; @@ -299,7 +283,7 @@ impl StateResolution { /// Returns a Vec of deduped EventIds that appear in some chains but no others. pub fn get_auth_chain_diff( - &mut self, + &self, room_id: &RoomId, state_sets: &[StateMap], store: &dyn StateStore, @@ -321,7 +305,7 @@ impl StateResolution { } pub fn reverse_topological_power_sort( - &mut self, + &self, room_id: &RoomId, power_events: &[EventId], event_map: &mut EventMap, @@ -379,7 +363,7 @@ impl StateResolution { /// `key_fn` is used as a tie breaker. The tie breaker happens based on /// power level, age, and event_id. pub fn lexicographical_topological_sort( - &mut self, + &self, graph: &BTreeMap>, key_fn: F, ) -> Vec @@ -446,7 +430,7 @@ impl StateResolution { } fn get_power_level_for_sender( - &mut self, + &self, room_id: &RoomId, event_id: &EventId, event_map: &mut EventMap, @@ -506,7 +490,7 @@ impl StateResolution { } fn iterative_auth_check( - &mut self, + &self, room_id: &RoomId, room_version: &RoomVersionId, power_events: &[EventId], @@ -581,7 +565,7 @@ impl StateResolution { /// NOTE we rely on the `event_map` beign full at this point. /// TODO is this ok? fn mainline_sort( - &mut self, + &self, room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, @@ -659,7 +643,7 @@ impl StateResolution { // TODO make `event` not clone every loop fn get_mainline_depth( - &mut self, + &self, room_id: &RoomId, mut event: Option, mainline_map: &EventMap, @@ -692,7 +676,7 @@ impl StateResolution { } fn add_event_and_auth_chain_to_graph( - &mut self, + &self, room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, @@ -726,7 +710,7 @@ impl StateResolution { /// TODO update self if we go that route just as event_map will be updated fn _get_event( - &mut self, + &self, _room_id: &RoomId, ev_id: &EventId, event_map: &mut EventMap, diff --git a/tests/auth_ids.rs b/tests/auth_ids.rs index 45bbd1da..be1d8d7e 100644 --- a/tests/auth_ids.rs +++ b/tests/auth_ids.rs @@ -34,7 +34,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let store = TestStore(RefCell::new( INITIAL_EVENTS() @@ -568,7 +568,7 @@ fn ban_with_auth_chains() { #[test] fn base_with_auth_chains() { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let store = TestStore(RefCell::new(INITIAL_EVENTS())); @@ -608,7 +608,7 @@ fn base_with_auth_chains() { #[test] fn ban_with_auth_chains2() { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let init = INITIAL_EVENTS(); let ban = BAN_STATE_SET(); diff --git a/tests/state_res.rs b/tests/state_res.rs index ba4393f8..ab08b0cc 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -278,7 +278,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let store = TestStore(RefCell::new( INITIAL_EVENTS() @@ -691,7 +691,7 @@ fn topic_setting() { #[test] fn test_event_map_none() { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let store = TestStore(RefCell::new(btreemap! {})); @@ -715,7 +715,7 @@ fn test_event_map_none() { #[test] fn test_lexicographical_sort() { - let mut resolver = StateResolution::default(); + let resolver = StateResolution::default(); let graph = btreemap! { event_id("l") => vec![event_id("o")], From 185047918a8f5ededc4195cf7370bf92d196c67e Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 5 Aug 2020 23:34:49 -0400 Subject: [PATCH 020/130] StateResolution's methods take &self now + more clean up --- Cargo.toml | 2 -- src/lib.rs | 6 ++++++ tests/{auth_ids.rs => res_with_auth_ids.rs} | 0 3 files changed, 6 insertions(+), 2 deletions(-) rename tests/{auth_ids.rs => res_with_auth_ids.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index fc28c60d..d47e54ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ repository = "https://github.com/ruma/state-res" [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" tracing = "0.1.16" @@ -29,7 +28,6 @@ branch = "pdu-deserialize" features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] -lazy_static = "1.4.0" criterion = "0.3.3" [[bench]] diff --git a/src/lib.rs b/src/lib.rs index a0867678..bcf5ef67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,6 +352,12 @@ impl StateResolution { (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) ); + // count_0.sort_by(|(x, _), (y, _)| { + // x.power_level + // .cmp(&a.power_level) + // .then_with(|| x.origin_server.ts.cmp(&y.origin_server_ts)) + // .then_with(|| x.event_id.cmp(&y.event_id)) + // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. diff --git a/tests/auth_ids.rs b/tests/res_with_auth_ids.rs similarity index 100% rename from tests/auth_ids.rs rename to tests/res_with_auth_ids.rs From 9a388fc8135f6dee7acbad13dc22f64a7173a9a7 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 11 Aug 2020 23:22:20 -0400 Subject: [PATCH 021/130] Bump ruma to same rev as conduit federation-p2p branch --- Cargo.toml | 4 ++-- src/state_event.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d47e54ca..90085ecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,8 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.8" [dependencies.ruma] -git = "https://github.com/DevinR528/ruma" -branch = "pdu-deserialize" +git = "https://github.com/ruma/ruma" +rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] diff --git a/src/state_event.rs b/src/state_event.rs index f2f8cd95..0f8e7154 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -173,7 +173,7 @@ impl StateEvent { pub fn prev_event_ids(&self) -> Vec { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.prev_events.to_vec(), + Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, Self::Sync(ev) => match ev { @@ -188,7 +188,7 @@ impl StateEvent { pub fn auth_events(&self) -> Vec { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.auth_events.to_vec(), + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, Self::Sync(ev) => match ev { From 1509ef45c188aa618fd716da24b5102f4b86a41d Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 11 Aug 2020 23:40:22 -0400 Subject: [PATCH 022/130] Update ruma depricated and use Option for state_key --- benches/state_res_bench.rs | 49 ++++++++++---------------------------- src/event_auth.rs | 40 +++++++++++++++---------------- src/lib.rs | 8 +++---- src/room_version.rs | 34 +++++++++++--------------- src/state_event.rs | 1 + tests/res_with_auth_ids.rs | 34 ++++++++++---------------- tests/state_res.rs | 39 ++++++++++-------------------- 7 files changed, 75 insertions(+), 130 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 555677d6..fb25ef0a 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -57,7 +57,7 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { b.iter(|| { let _resolved = match resolver.resolve( &room_id(), - &RoomVersionId::version_2(), + &RoomVersionId::Version2, &[state_at_bob.clone(), state_at_charlie.clone()], None, &store, @@ -91,13 +91,8 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| { - ( - (ev.kind(), ev.state_key().unwrap()), - ev.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .collect::>(); let state_set_b = [ inner.get(&event_id("CREATE")).unwrap(), @@ -109,18 +104,13 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| { - ( - (ev.kind(), ev.state_key().unwrap()), - ev.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .collect::>(); b.iter(|| { let _resolved = match resolver.resolve( &room_id(), - &RoomVersionId::version_2(), + &RoomVersionId::Version2, &[state_set_a.clone(), state_set_b.clone()], Some(inner.clone()), &store, @@ -302,23 +292,13 @@ impl TestStore { let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); let expected = [ &create_event, @@ -328,13 +308,8 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); (state_at_bob, state_at_charlie, expected) } diff --git a/src/event_auth.rs b/src/event_auth.rs index c5a630eb..af1e68e4 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -22,35 +22,35 @@ pub enum RedactAllowed { No, } -pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> { +pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, Option)> { 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()), + (EventType::RoomPowerLevels, Some("".to_string())), + (EventType::RoomMember, Some(event.sender().to_string())), + (EventType::RoomCreate, Some("".to_string())), ]; if event.kind() == EventType::RoomMember { if let Ok(content) = event.deserialize_content::() { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { - let key = (EventType::RoomJoinRules, "".into()); + let key = (EventType::RoomJoinRules, Some("".into())); if !auth_types.contains(&key) { auth_types.push(key) } } // TODO what when we don't find a state_key - let key = (EventType::RoomMember, event.state_key().unwrap()); + let key = (EventType::RoomMember, event.state_key()); if !auth_types.contains(&key) { auth_types.push(key) } if content.membership == MembershipState::Invite { if let Some(t_id) = content.third_party_invite { - let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); + let key = (EventType::RoomThirdPartyInvite, Some(t_id.signed.token)); if !auth_types.contains(&key) { auth_types.push(key) } @@ -137,7 +137,7 @@ pub fn auth_check( // 3. If event does not have m.room.create in auth_events reject. if auth_events - .get(&(EventType::RoomCreate, "".into())) + .get(&(EventType::RoomCreate, Some("".into()))) .is_none() { tracing::warn!("no m.room.create event in auth chain"); @@ -238,7 +238,7 @@ pub fn auth_check( // 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) -> bool { - let creation_event = auth_events.get(&(EventType::RoomCreate, "".into())); + let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); if let Some(ev) = creation_event { if let Some(fed) = ev.content().get("m.federate") { fed == "true" @@ -263,7 +263,7 @@ fn is_membership_change_allowed( // 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 Some(create) = auth_events.get(&(EventType::RoomCreate, Some("".into()))) { if let Ok(create_ev) = create.deserialize_content::() { if event.state_key() == Some(create_ev.creator.to_string()) { @@ -283,19 +283,19 @@ fn is_membership_change_allowed( return Some(false); } - let key = (EventType::RoomMember, event.sender().to_string()); + let key = (EventType::RoomMember, Some(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 key = (EventType::RoomMember, Some(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 key = (EventType::RoomJoinRules, Some("".to_string())); let join_rules_event = auth_events.get(&key); let mut join_rule = JoinRule::Invite; @@ -436,7 +436,7 @@ fn check_event_sender_in_room( event: &StateEvent, auth_events: &StateMap, ) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, event.sender().to_string()))?; + let mem = auth_events.get(&(EventType::RoomMember, Some(event.sender().to_string())))?; // TODO this is check_membership a helper fn in synapse but it does this Some( mem.deserialize_content::() @@ -448,7 +448,7 @@ fn check_event_sender_in_room( /// Is the user allowed to send a specific event. fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { - let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); + let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); let send_level = get_send_level(event.kind(), event.state_key(), ple); let user_level = get_user_power_level(event.sender(), auth_events); @@ -480,7 +480,7 @@ fn check_power_levels( ) -> Option { use itertools::Itertools; - let key = (power_event.kind(), power_event.state_key().unwrap()); + let key = (power_event.kind(), power_event.state_key()); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state @@ -629,7 +629,7 @@ fn check_redaction( return Some(RedactAllowed::CanRedact); } - if room_version.is_version_1() { + if let RoomVersionId::Version1 = room_version { if redaction_event.event_id() == redaction_event.redacts() { return Some(RedactAllowed::OwnEvent); } @@ -659,7 +659,7 @@ fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) - } fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { - let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, "".into())); + let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { // TODO do this the right way and deserialize if let Some(level) = pl.content().get(name) { @@ -673,7 +673,7 @@ fn get_named_level(auth_events: &StateMap, name: &str, default: i64) } fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> i64 { - if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { + if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { if let Ok(content) = pl.deserialize_content::() { if let Some(level) = content.users.get(user_id) { @@ -686,7 +686,7 @@ fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> } } else { // if no power level event found the creator gets 100 everyone else gets 0 - let key = (EventType::RoomCreate, "".into()); + let key = (EventType::RoomCreate, Some("".into())); if let Some(create) = auth_events.get(&key) { if let Ok(c) = create.deserialize_content::() { if &c.creator == user_id { diff --git a/src/lib.rs b/src/lib.rs index bcf5ef67..e3085893 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub enum ResolutionResult { } /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, String), T>; +pub type StateMap = BTreeMap<(EventType, Option), T>; /// A mapping of `EventId` to `T`, usually a `StateEvent`. pub type EventMap = BTreeMap; @@ -191,7 +191,7 @@ impl StateResolution { .collect::>() ); - let power_event = resolved.get(&(EventType::RoomPowerLevels, "".into())); + let power_event = resolved.get(&(EventType::RoomPowerLevels, Some("".into()))); tracing::debug!("PL {:?}", power_event); @@ -526,7 +526,7 @@ impl StateResolution { if let Some(ev) = self._get_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because for ruma ?? - auth_events.insert((ev.kind(), ev.state_key().unwrap()), ev); + auth_events.insert((ev.kind(), ev.state_key()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); } @@ -547,7 +547,7 @@ impl StateResolution { .map_err(Error::TempString)? { // add event to resolved state map - resolved_state.insert((event.kind(), event.state_key().unwrap()), event_id.clone()); + resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); } else { // synapse passes here on AuthError. We do not add this event to resolved_state. tracing::warn!( diff --git a/src/room_version.rs b/src/room_version.rs index 4c527713..1cf86b92 100644 --- a/src/room_version.rs +++ b/src/room_version.rs @@ -50,26 +50,20 @@ pub struct RoomVersion { 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") + match version { + RoomVersionId::Version1 => Self::version_1(), + RoomVersionId::Version2 => Self::version_2(), + RoomVersionId::Version3 => Self::version_3(), + RoomVersionId::Version4 => Self::version_4(), + RoomVersionId::Version5 => Self::version_5(), + RoomVersionId::Version6 => Self::version_6(), + _ => panic!("unspec'ed room version"), } } fn version_1() -> Self { Self { - version: RoomVersionId::version_1(), + version: RoomVersionId::Version1, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V1, state_res: StateResolutionVersion::V1, @@ -82,7 +76,7 @@ impl RoomVersion { fn version_2() -> Self { Self { - version: RoomVersionId::version_2(), + version: RoomVersionId::Version2, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V1, state_res: StateResolutionVersion::V2, @@ -95,7 +89,7 @@ impl RoomVersion { fn version_3() -> Self { Self { - version: RoomVersionId::version_3(), + version: RoomVersionId::Version3, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V2, state_res: StateResolutionVersion::V2, @@ -108,7 +102,7 @@ impl RoomVersion { fn version_4() -> Self { Self { - version: RoomVersionId::version_4(), + version: RoomVersionId::Version4, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V3, state_res: StateResolutionVersion::V2, @@ -121,7 +115,7 @@ impl RoomVersion { fn version_5() -> Self { Self { - version: RoomVersionId::version_5(), + version: RoomVersionId::Version5, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V3, state_res: StateResolutionVersion::V2, @@ -134,7 +128,7 @@ impl RoomVersion { fn version_6() -> Self { Self { - version: RoomVersionId::version_6(), + version: RoomVersionId::Version6, disposition: RoomDisposition::Stable, event_format: EventFormatVersion::V3, state_res: StateResolutionVersion::V2, diff --git a/src/state_event.rs b/src/state_event.rs index 0f8e7154..751098f8 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -101,6 +101,7 @@ impl StateEvent { } } pub fn event_id(&self) -> Option<&EventId> { + println!("{:?}", self); match self { Self::Full(ev) => match ev { Pdu::RoomV1Pdu(ev) => Some(&ev.event_id), diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index be1d8d7e..6d374a5b 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -102,14 +102,14 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .iter() .map(|map| map .iter() - .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) .collect::>()) .collect::>() ); let resolved = resolver.resolve( &room_id(), - &RoomVersionId::version_1(), + &RoomVersionId::Version1, &state_sets, Some(event_map.clone()), &store, @@ -135,7 +135,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); // we know there is a state_key unwrap OK - let key = fake_event.state_key().unwrap().clone(); + let key = fake_event.state_key().clone(); state_after.insert((ty, key), event_id.clone()); } @@ -173,7 +173,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: event_map.insert(event_id.clone(), event); } - let mut expected_state = BTreeMap::new(); + let mut expected_state = StateMap::new(); for node in expected_state_ids { let ev = event_map.get(&node).expect(&format!( "{} not found in {:?}", @@ -184,7 +184,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .collect::>(), )); - let key = (ev.kind(), ev.state_key().unwrap()); + let key = (ev.kind(), ev.state_key()); expected_state.insert(key, node); } @@ -573,7 +573,7 @@ fn base_with_auth_chains() { let store = TestStore(RefCell::new(INITIAL_EVENTS())); let resolved: BTreeMap<_, EventId> = - match resolver.resolve(&room_id(), &RoomVersionId::version_2(), &[], None, &store) { + match resolver.resolve(&room_id(), &RoomVersionId::Version2, &[], None, &store) { Ok(ResolutionResult::Resolved(state)) => state, Err(e) => panic!("{}", e), _ => panic!("conflicted state left"), @@ -627,12 +627,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| { - ( - (ev.kind(), ev.state_key().unwrap()), - ev.event_id().unwrap().clone(), - ) - }) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) .collect::>(); let state_set_b = [ @@ -645,17 +640,12 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| { - ( - (ev.kind(), ev.state_key().unwrap()), - ev.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .collect::>(); - let resolved: BTreeMap<_, EventId> = match resolver.resolve( + let resolved: StateMap = match resolver.resolve( &room_id(), - &RoomVersionId::version_2(), + &RoomVersionId::Version2, &[state_set_a, state_set_b], None, &store, @@ -669,7 +659,7 @@ fn ban_with_auth_chains2() { "{:#?}", resolved .iter() - .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) .collect::>() ); diff --git a/tests/state_res.rs b/tests/state_res.rs index ab08b0cc..d62bc132 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -346,14 +346,14 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .iter() .map(|map| map .iter() - .map(|((ty, key), id)| format!("(({}{}), {})", ty, key, id)) + .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) .collect::>()) .collect::>() ); let resolved = resolver.resolve( &room_id(), - &RoomVersionId::version_1(), + &RoomVersionId::Version1, &state_sets, Some(event_map.clone()), &store, @@ -379,7 +379,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); // we know there is a state_key unwrap OK - let key = fake_event.state_key().unwrap().clone(); + let key = fake_event.state_key().clone(); state_after.insert((ty, key), event_id.clone()); } @@ -417,7 +417,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: event_map.insert(event_id.clone(), event); } - let mut expected_state = BTreeMap::new(); + let mut expected_state = StateMap::new(); for node in expected_state_ids { let ev = event_map.get(&node).expect(&format!( "{} not found in {:?}", @@ -428,7 +428,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .collect::>(), )); - let key = (ev.kind(), ev.state_key().unwrap()); + let key = (ev.kind(), ev.state_key()); expected_state.insert(key, node); } @@ -700,7 +700,7 @@ fn test_event_map_none() { let resolved = match resolver.resolve( &room_id(), - &RoomVersionId::version_2(), + &RoomVersionId::Version2, &[state_at_bob, state_at_charlie], None, &store, @@ -904,23 +904,13 @@ impl TestStore { let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); let expected = [ &create_event, @@ -930,13 +920,8 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| { - ( - (e.kind(), e.state_key().unwrap()), - e.event_id().unwrap().clone(), - ) - }) - .collect::>(); + .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .collect::>(); (state_at_bob, state_at_charlie, expected) } From 5a45970266d99cc163639ad5016740958aa6f9b8 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 11 Aug 2020 23:53:33 -0400 Subject: [PATCH 023/130] Update how PDU works this needs to be fixed TODO I have just added a hack to make the Pdu deserialize correctly in the tests this has to be fixed!! --- benches/state_res_bench.rs | 17 +++++++++++++++++ src/state_event.rs | 2 +- tests/res_with_auth_ids.rs | 17 +++++++++++++++++ tests/state_res.rs | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index fb25ef0a..67e48450 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -14,6 +14,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; use ruma::{ events::{ + pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -388,11 +389,27 @@ where .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/src/state_event.rs b/src/state_event.rs index 751098f8..abcd2fbd 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -266,7 +266,7 @@ impl<'de> de::Deserialize<'de> for StateEvent { Some(unsigned) if unsigned.redacted_because.is_some() => { panic!("TODO deal with redacted events") } - _ => StateEvent::Full(from_raw_json_value(&json)?), + _ => StateEvent::Full(Pdu::RoomV1Pdu(from_raw_json_value(&json)?)), }) } else { Ok(match unsigned { diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 6d374a5b..71bdfdd6 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -10,6 +10,7 @@ use std::{ use ruma::{ events::{ + pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -360,11 +361,27 @@ where .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/tests/state_res.rs b/tests/state_res.rs index d62bc132..80bf3500 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -10,6 +10,7 @@ use std::{ use maplit::btreemap; use ruma::{ events::{ + pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -103,11 +104,27 @@ where .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) .collect::>(); let json = if let Some(state_key) = state_key { From 5f77bc11a2d57cee3bc977dff143cea5eca4df45 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 12 Aug 2020 18:24:58 -0400 Subject: [PATCH 024/130] Update ruma with conduit, make event_id return EventId not Option<> --- Cargo.toml | 20 +++++++---- README.md | 2 +- benches/state_res_bench.rs | 46 +++++++++++++------------- src/event_auth.rs | 12 +++---- src/lib.rs | 48 ++++++++++----------------- src/state_event.rs | 40 ++++++++++++++++++---- src/state_store.rs | 68 +++++++++++++++++++++++++++++++++++--- tests/res_with_auth_ids.rs | 39 ++++++++++------------ tests/state_res.rs | 56 +++++++++++++++---------------- 9 files changed, 202 insertions(+), 129 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90085ecd..6aef52eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,19 +14,25 @@ repository = "https://github.com/ruma/state-res" # 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" -serde = { version = "1.0.114", features = ["derive"] } -serde_json = "1.0.56" -tracing = "0.1.16" +js_int = "0.1.9" +serde = { version = "1.0.115", features = ["derive"] } +serde_json = "1.0.57" +tracing = "0.1.19" maplit = "1.0.2" thiserror = "1.0.20" -tracing-subscriber = "0.2.8" +tracing-subscriber = "0.2.11" + +# [dependencies.ruma] +# git = "https://github.com/ruma/ruma" +# rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" +# features = ["client-api", "federation-api", "appservice-api"] [dependencies.ruma] -git = "https://github.com/ruma/ruma" -rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" +git = "https://github.com/timokoesters/ruma" +branch = "timo-fixes" features = ["client-api", "federation-api", "appservice-api"] + [dev-dependencies] criterion = "0.3.3" diff --git a/README.md b/README.md index f751df51..decc9685 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,4 @@ trait StateStore { The `StateStore` trait is an abstraction around what ever database your server (or maybe even client) uses to store __P__[]()ersistant __D__[]()ata __U__[]()nits. -We use `ruma`s types when deserializing any PDU or it's contents which helps avoid a lot of type checking logic [synapse](https://github.com/matrix-org/synapse) must do while authenticating event chains. \ No newline at end of file +We use `ruma`s types when deserializing any PDU or it's contents which helps avoid a lot of type checking logic [synapse](https://github.com/matrix-org/synapse) must do while authenticating event chains. diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 67e48450..7f501d47 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -40,8 +40,8 @@ fn lexico_topo_sort(c: &mut Criterion) { b.iter(|| { let resolver = StateResolution::default(); - let _ = resolver - .lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))); + let _ = + resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())); }) }); } @@ -92,7 +92,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) .collect::>(); let state_set_b = [ @@ -105,7 +105,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) .collect::>(); b.iter(|| { @@ -142,7 +142,7 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, events: &[EventId]) -> Result, String> { + fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { Ok(self .0 .borrow() @@ -153,7 +153,7 @@ impl StateStore for TestStore { .collect()) } - fn get_event(&self, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) @@ -178,7 +178,7 @@ impl StateStore for TestStore { result.push(ev_id.clone()); - let event = self.get_event(&ev_id).unwrap(); + let event = self.get_event(room_id, &ev_id).unwrap(); stack.extend(event.auth_events()); } @@ -232,7 +232,7 @@ impl TestStore { &[], &[], ); - let cre = create_event.event_id().unwrap().clone(); + let cre = create_event.event_id(); self.0 .borrow_mut() .insert(cre.clone(), create_event.clone()); @@ -248,7 +248,7 @@ impl TestStore { ); self.0 .borrow_mut() - .insert(alice_mem.event_id().unwrap().clone(), alice_mem.clone()); + .insert(alice_mem.event_id(), alice_mem.clone()); let join_rules = to_pdu_event( "IJR", @@ -256,12 +256,12 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id().unwrap().clone()], - &[alice_mem.event_id().unwrap().clone()], + &[cre.clone(), alice_mem.event_id()], + &[alice_mem.event_id()], ); self.0 .borrow_mut() - .insert(join_rules.event_id().unwrap().clone(), join_rules.clone()); + .insert(join_rules.event_id(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -271,12 +271,12 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id().unwrap().clone()], - &[join_rules.event_id().unwrap().clone()], + &[cre.clone(), join_rules.event_id()], + &[join_rules.event_id()], ); self.0 .borrow_mut() - .insert(bob_mem.event_id().unwrap().clone(), bob_mem.clone()); + .insert(bob_mem.event_id(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -284,21 +284,21 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id().unwrap().clone()], - &[join_rules.event_id().unwrap().clone()], + &[cre, join_rules.event_id()], + &[join_rules.event_id()], ); self.0 .borrow_mut() - .insert(charlie_mem.event_id().unwrap().clone(), charlie_mem.clone()); + .insert(charlie_mem.event_id(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); let expected = [ @@ -309,7 +309,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); (state_at_bob, state_at_charlie, expected) @@ -525,7 +525,7 @@ fn INITIAL_EVENTS() -> BTreeMap { ), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } @@ -571,6 +571,6 @@ fn BAN_STATE_SET() -> BTreeMap { ), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } diff --git a/src/event_auth.rs b/src/event_auth.rs index af1e68e4..747e4289 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -68,10 +68,7 @@ pub fn auth_check( auth_events: StateMap, do_sig_check: bool, ) -> Option { - tracing::info!( - "auth_check beginning for {}", - event.event_id().unwrap().as_str() - ); + tracing::info!("auth_check beginning for {}", event.event_id().as_str()); // don't let power from other rooms be used for auth_event in auth_events.values() { @@ -455,7 +452,7 @@ fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Opt tracing::debug!( "{} snd {} usr {}", - event.event_id().unwrap().to_string(), + event.event_id().to_string(), send_level, user_level ); @@ -630,7 +627,10 @@ fn check_redaction( } if let RoomVersionId::Version1 = room_version { - if redaction_event.event_id() == redaction_event.redacts() { + // are the redacter and redactee in the same domain + if Some(redaction_event.event_id().server_name()) + == redaction_event.redacts().map(|id| id.server_name()) + { return Some(RedactAllowed::OwnEvent); } } else { diff --git a/src/lib.rs b/src/lib.rs index e3085893..2a1e1c6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,7 @@ impl StateResolution { // gather missing events for the event_map let events = store .get_events( + room_id, &all_conflicted .iter() // we only want the events we don't know about yet @@ -101,11 +102,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend( - events - .into_iter() - .flat_map(|ev| Some((ev.event_id()?.clone(), ev))), - ); + event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -114,10 +111,7 @@ impl StateResolution { if event.room_id() != Some(room_id) { return Err(Error::TempString(format!( "resolving event {} in room {}, when correct room is {}", - event - .event_id() - .map(|id| id.as_str()) - .unwrap_or("`unknown`"), + event.event_id(), event.room_id().map(|id| id.as_str()).unwrap_or("`unknown`"), room_id.as_str() ))); @@ -307,7 +301,7 @@ impl StateResolution { pub fn reverse_topological_power_sort( &self, room_id: &RoomId, - power_events: &[EventId], + events_to_sort: &[EventId], event_map: &mut EventMap, store: &dyn StateStore, auth_diff: &[EventId], @@ -315,7 +309,7 @@ impl StateResolution { tracing::debug!("reverse topological sort of power events"); let mut graph = BTreeMap::new(); - for (idx, event_id) in power_events.iter().enumerate() { + for (idx, event_id) in events_to_sort.iter().enumerate() { self.add_event_and_auth_chain_to_graph( room_id, &mut graph, event_id, event_map, store, auth_diff, ); @@ -347,10 +341,7 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!( - "{:?}", - (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) - ); + tracing::debug!("{:?}", (-*pl, *ev.origin_server_ts(), ev.event_id())); // count_0.sort_by(|(x, _), (y, _)| { // x.power_level @@ -361,7 +352,7 @@ impl StateResolution { // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, *ev.origin_server_ts(), ev.event_id().cloned()) + (-*pl, *ev.origin_server_ts(), ev.event_id()) }) } @@ -374,7 +365,7 @@ impl StateResolution { key_fn: F, ) -> Vec where - F: Fn(&EventId) -> (i64, SystemTime, Option), + F: Fn(&EventId) -> (i64, SystemTime, EventId), { tracing::info!("starting lexicographical topological sort"); // NOTE: an event that has no incoming edges happened most recently, @@ -458,8 +449,8 @@ impl StateResolution { } if pl.is_none() { - for aid in store.get_event(event_id).unwrap().auth_events() { - if let Ok(aev) = store.get_event(&aid) { + for aid in store.get_event(room_id, event_id).unwrap().auth_events() { + if let Ok(aev) = store.get_event(room_id, &aid) { if aev.is_type_and_key(EventType::RoomCreate, "") { if let Ok(content) = aev .deserialize_content::() @@ -541,7 +532,8 @@ impl StateResolution { } } - tracing::debug!("event to check {:?}", event.event_id().unwrap().to_string()); + tracing::debug!("event to check {:?}", event.event_id().to_string()); + if event_auth::auth_check(room_version, &event, auth_events, false) .ok_or("Auth check failed due to deserialization most likely".to_string()) .map_err(Error::TempString)? @@ -657,14 +649,10 @@ impl StateResolution { store: &dyn StateStore, ) -> usize { while let Some(sort_ev) = event { - tracing::debug!( - "mainline event_id {}", - sort_ev.event_id().unwrap().to_string() - ); - if let Some(id) = sort_ev.event_id() { - if let Some(depth) = mainline_map.get(id) { - return *depth; - } + tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); + let id = sort_ev.event_id(); + if let Some(depth) = mainline_map.get(&id) { + return *depth; } let auth_events = sort_ev.auth_events(); @@ -717,14 +705,14 @@ impl StateResolution { /// TODO update self if we go that route just as event_map will be updated fn _get_event( &self, - _room_id: &RoomId, + room_id: &RoomId, ev_id: &EventId, event_map: &mut EventMap, store: &dyn StateStore, ) -> Option { // TODO can we cut down on the clones? if !event_map.contains_key(ev_id) { - let event = store.get_event(ev_id).ok()?; + let event = store.get_event(room_id, ev_id).ok()?; event_map.insert(ev_id.clone(), event.clone()); Some(event) } else { diff --git a/src/state_event.rs b/src/state_event.rs index abcd2fbd..34465d14 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::TryFrom}; use ruma::{ events::{ @@ -100,14 +100,27 @@ impl StateEvent { }, } } - pub fn event_id(&self) -> Option<&EventId> { - println!("{:?}", self); + pub fn event_id(&self) -> EventId { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => Some(&ev.event_id), - Pdu::RoomV3Pdu(_) => None, + Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), + Pdu::RoomV3Pdu(_) => EventId::try_from(&*format!( + "${}", + ruma::signatures::reference_hash( + &serde_json::to_value(&ev).expect("event is valid, we just created it") + ) + .expect("ruma can calculate reference hashes") + )) + .expect("ruma's reference hashes are valid event ids"), }, - Self::Sync(_) => None, + Self::Sync(ev) => EventId::try_from(&*format!( + "${}", + ruma::signatures::reference_hash( + &serde_json::to_value(&ev).expect("event is valid, we just created it") + ) + .expect("ruma can calculate reference hashes") + )) + .expect("ruma's reference hashes are valid event ids"), } } @@ -214,6 +227,21 @@ impl StateEvent { } } + pub fn unsigned(&self) -> &BTreeMap { + // CONFIRM: The only way this would fail is if we got bad json, it should fail in ruma + // before it fails here. + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.unsigned, + Pdu::RoomV3Pdu(ev) => &ev.unsigned, + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => &ev.unsigned, + PduStub::RoomV3PduStub(ev) => &ev.unsigned, + }, + } + } + pub fn signatures(&self) -> BTreeMap, BTreeMap> { match self { Self::Full(ev) => match ev { diff --git a/src/state_store.rs b/src/state_store.rs index 7881883a..80621bf0 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,25 +1,83 @@ +use std::collections::BTreeSet; + use ruma::identifiers::{EventId, RoomId}; use crate::StateEvent; pub trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, event_id: &EventId) -> Result; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, event_ids: &[EventId]) -> Result, String>; + fn get_events( + &self, + room_id: &RoomId, + event_ids: &[EventId], + ) -> Result, String> { + let mut events = vec![]; + for id in event_ids { + events.push(self.get_event(room_id, id)?); + } + Ok(events) + } /// Returns a Vec of the related auth events to the given `event`. fn auth_event_ids( &self, room_id: &RoomId, event_ids: &[EventId], - ) -> Result, String>; + ) -> Result, String> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(room_id, &ev_id).unwrap(); + + stack.extend(event.auth_events()); + } + + Ok(result) + } /// Returns a Vec representing the difference in auth chains of the given `events`. fn auth_chain_diff( &self, room_id: &RoomId, - event_id: Vec>, - ) -> Result, String>; + event_ids: Vec>, + ) -> Result, String> { + let mut chains = vec![]; + for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } } diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 71bdfdd6..94e35ba3 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -41,7 +41,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: INITIAL_EVENTS() .values() .chain(events) - .map(|ev| (ev.event_id().unwrap().clone(), ev.clone())) + .map(|ev| (ev.event_id(), ev.clone())) .collect(), )); @@ -53,8 +53,8 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id().unwrap().clone(), vec![]); - fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); + graph.insert(ev.event_id().clone(), vec![]); + fake_event_map.insert(ev.event_id().clone(), ev.clone()); } for pair in INITIAL_EDGES().windows(2) { @@ -78,11 +78,10 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // resolve the current state and add it to the state_at_event map then continue // on in "time" - for node in - resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) + for node in resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id().unwrap(); + let event_id = fake_event.event_id(); let prev_events = graph.get(&node).unwrap(); @@ -152,9 +151,9 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO The event is just remade, adding the auth_events and prev_events here // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; - let ev_id = e.event_id().unwrap(); + let ev_id = e.event_id(); let event = to_pdu_event( - &e.event_id().unwrap().to_string(), + &e.event_id().to_string(), e.sender().clone(), e.kind(), e.state_key().as_deref(), @@ -168,7 +167,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO // TODO we need to convert the `StateResolution::resolve` to use the event_map // because the user of this crate cannot update their DB's state. - *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); + *store.0.borrow_mut().get_mut(&ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); event_map.insert(event_id.clone(), event); @@ -206,7 +205,7 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, events: &[EventId]) -> Result, String> { + fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { Ok(self .0 .borrow() @@ -217,7 +216,7 @@ impl StateStore for TestStore { .collect()) } - fn get_event(&self, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) @@ -242,7 +241,7 @@ impl StateStore for TestStore { result.push(ev_id.clone()); - let event = self.get_event(&ev_id).unwrap(); + let event = self.get_event(room_id, &ev_id).unwrap(); stack.extend(event.auth_events()); } @@ -504,7 +503,7 @@ fn INITIAL_EVENTS() -> BTreeMap { ), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } @@ -558,7 +557,7 @@ fn BAN_STATE_SET() -> BTreeMap { ), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } @@ -599,11 +598,7 @@ fn base_with_auth_chains() { let resolved = resolved .values() .cloned() - .chain( - INITIAL_EVENTS() - .values() - .map(|e| e.event_id().unwrap().clone()), - ) + .chain(INITIAL_EVENTS().values().map(|e| e.event_id())) .collect::>(); let expected = vec![ @@ -644,7 +639,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) .collect::>(); let state_set_b = [ @@ -657,7 +652,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().unwrap().clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) .collect::>(); let resolved: StateMap = match resolver.resolve( @@ -725,7 +720,7 @@ fn JOIN_RULE() -> BTreeMap { ), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } diff --git a/tests/state_res.rs b/tests/state_res.rs index 80bf3500..399133d6 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -273,7 +273,7 @@ fn INITIAL_EVENTS() -> BTreeMap { to_init_pdu_event("END", zera(), EventType::RoomMessage, None, json!({})), ] .into_iter() - .map(|ev| (ev.event_id().unwrap().clone(), ev)) + .map(|ev| (ev.event_id(), ev)) .collect() } @@ -301,7 +301,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: INITIAL_EVENTS() .values() .chain(events) - .map(|ev| (ev.event_id().unwrap().clone(), ev.clone())) + .map(|ev| (ev.event_id(), ev.clone())) .collect(), )); @@ -313,8 +313,8 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id().unwrap().clone(), vec![]); - fake_event_map.insert(ev.event_id().unwrap().clone(), ev.clone()); + graph.insert(ev.event_id(), vec![]); + fake_event_map.insert(ev.event_id(), ev.clone()); } for pair in INITIAL_EDGES().windows(2) { @@ -338,11 +338,10 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // resolve the current state and add it to the state_at_event map then continue // on in "time" - for node in - resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))) + for node in resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id().unwrap(); + let event_id = fake_event.event_id(); let prev_events = graph.get(&node).unwrap(); @@ -412,9 +411,9 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO The event is just remade, adding the auth_events and prev_events here // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; - let ev_id = e.event_id().unwrap(); + let ev_id = e.event_id(); let event = to_pdu_event( - &e.event_id().unwrap().to_string(), + &e.event_id().to_string(), e.sender().clone(), e.kind(), e.state_key().as_deref(), @@ -428,7 +427,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO // TODO we need to convert the `StateResolution::resolve` to use the event_map // because the user of this crate cannot update their DB's state. - *store.0.borrow_mut().get_mut(ev_id).unwrap() = event.clone(); + *store.0.borrow_mut().get_mut(&ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); event_map.insert(event_id.clone(), event); @@ -742,8 +741,7 @@ fn test_lexicographical_sort() { event_id("p") => vec![event_id("o")], }; - let res = - resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, Some(id.clone()))); + let res = resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())); assert_eq!( vec!["o", "l", "n", "m", "p"], @@ -763,7 +761,7 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, events: &[EventId]) -> Result, String> { + fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { Ok(self .0 .borrow() @@ -774,7 +772,7 @@ impl StateStore for TestStore { .collect()) } - fn get_event(&self, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) @@ -799,7 +797,7 @@ impl StateStore for TestStore { result.push(ev_id.clone()); - let event = self.get_event(&ev_id).unwrap(); + let event = self.get_event(room_id, &ev_id).unwrap(); stack.extend(event.auth_events()); } @@ -860,7 +858,7 @@ impl TestStore { &[], &[], ); - let cre = create_event.event_id().unwrap().clone(); + let cre = create_event.event_id(); self.0 .borrow_mut() .insert(cre.clone(), create_event.clone()); @@ -876,7 +874,7 @@ impl TestStore { ); self.0 .borrow_mut() - .insert(alice_mem.event_id().unwrap().clone(), alice_mem.clone()); + .insert(alice_mem.event_id(), alice_mem.clone()); let join_rules = to_pdu_event( "IJR", @@ -884,12 +882,12 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id().unwrap().clone()], - &[alice_mem.event_id().unwrap().clone()], + &[cre.clone(), alice_mem.event_id()], + &[alice_mem.event_id()], ); self.0 .borrow_mut() - .insert(join_rules.event_id().unwrap().clone(), join_rules.clone()); + .insert(join_rules.event_id(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -899,12 +897,12 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id().unwrap().clone()], - &[join_rules.event_id().unwrap().clone()], + &[cre.clone(), join_rules.event_id()], + &[join_rules.event_id()], ); self.0 .borrow_mut() - .insert(bob_mem.event_id().unwrap().clone(), bob_mem.clone()); + .insert(bob_mem.event_id(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -912,21 +910,21 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id().unwrap().clone()], - &[join_rules.event_id().unwrap().clone()], + &[cre, join_rules.event_id()], + &[join_rules.event_id()], ); self.0 .borrow_mut() - .insert(charlie_mem.event_id().unwrap().clone(), charlie_mem.clone()); + .insert(charlie_mem.event_id(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); let expected = [ @@ -937,7 +935,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().unwrap().clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id())) .collect::>(); (state_at_bob, state_at_charlie, expected) From 484f48dc41259296ab31e1953000c206b9806910 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 12 Aug 2020 18:39:33 -0400 Subject: [PATCH 025/130] Export all of the event_auth mod --- src/event_auth.rs | 23 ++++++++++++----------- src/lib.rs | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 747e4289..abf00573 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -234,7 +234,7 @@ pub fn auth_check( // 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) -> bool { +pub fn can_federate(auth_events: &StateMap) -> bool { let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); if let Some(ev) = creation_event { if let Some(fed) = ev.content().get("m.federate") { @@ -248,7 +248,7 @@ fn can_federate(auth_events: &StateMap) -> bool { } /// Dose the user who sent this member event have required power levels to do so. -fn is_membership_change_allowed( +pub fn is_membership_change_allowed( event: &StateEvent, auth_events: &StateMap, ) -> Option { @@ -429,7 +429,7 @@ fn is_membership_change_allowed( /// Is the event's sender in the room that they sent the event to. /// /// A return value of None is not a failure -fn check_event_sender_in_room( +pub fn check_event_sender_in_room( event: &StateEvent, auth_events: &StateMap, ) -> Option { @@ -444,7 +444,7 @@ fn check_event_sender_in_room( } /// Is the user allowed to send a specific event. -fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { +pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); let send_level = get_send_level(event.kind(), event.state_key(), ple); @@ -470,7 +470,7 @@ fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Opt } /// Confirm that the event sender has the required power levels. -fn check_power_levels( +pub fn check_power_levels( room_version: &RoomVersionId, power_event: &StateEvent, auth_events: &StateMap, @@ -614,7 +614,7 @@ fn get_deserialize_levels( } /// Does the event redacting come from a user with enough power to redact the given event. -fn check_redaction( +pub fn check_redaction( room_version: &RoomVersionId, redaction_event: &StateEvent, auth_events: &StateMap, @@ -644,7 +644,7 @@ fn check_redaction( /// 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 { +pub fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) -> bool { if let Some(event) = member_event { if let Ok(content) = serde_json::from_value::(event.content().clone()) @@ -658,7 +658,7 @@ fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) - } } -fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { +pub fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { // TODO do this the right way and deserialize @@ -672,7 +672,7 @@ fn get_named_level(auth_events: &StateMap, name: &str, default: i64) } } -fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> i64 { +pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> i64 { if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { if let Ok(content) = pl.deserialize_content::() { @@ -703,7 +703,7 @@ fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> } } -fn get_send_level( +pub fn get_send_level( e_type: EventType, state_key: Option, power_lvl: Option<&StateEvent>, @@ -733,6 +733,7 @@ fn get_send_level( } } -fn verify_third_party_invite(_event: &StateEvent, _auth_events: &StateMap) -> bool { +/// TODO this is unimplemented +pub fn verify_third_party_invite(_event: &StateEvent, _auth_events: &StateMap) -> bool { unimplemented!("impl third party invites") } diff --git a/src/lib.rs b/src/lib.rs index 2a1e1c6c..23571777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,8 +13,8 @@ use ruma::{ }; mod error; -mod event_auth; -mod room_version; +pub mod event_auth; +pub mod room_version; mod state_event; mod state_store; From 6e0edce35acb781dbfff3dac5bc6f32270a291e4 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 13 Aug 2020 01:15:57 -0400 Subject: [PATCH 026/130] Continue to update to keep compatibility with conduit --- src/event_auth.rs | 59 +++++++++++++++++++------------------- src/lib.rs | 2 +- src/state_event.rs | 18 ++++++++++++ tests/res_with_auth_ids.rs | 2 +- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index abf00573..ace434de 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -10,7 +10,11 @@ use ruma::{ }; use serde_json::json; -use crate::{room_version::RoomVersion, state_event::StateEvent, StateMap}; +use crate::{ + room_version::RoomVersion, + state_event::{Requester, StateEvent}, + StateMap, +}; /// Represents the 3 event redaction outcomes. pub enum RedactAllowed { @@ -178,7 +182,7 @@ pub fn auth_check( if event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if !is_membership_change_allowed(event, &auth_events)? { + if !is_membership_change_allowed(event.to_requester(), &auth_events)? { return Some(false); } @@ -249,21 +253,21 @@ pub fn can_federate(auth_events: &StateMap) -> bool { /// Dose the user who sent this member event have required power levels to do so. pub fn is_membership_change_allowed( - event: &StateEvent, + user: Requester<'_>, auth_events: &StateMap, ) -> Option { - let content = event - .deserialize_content::() - .ok() - .unwrap(); + let content = + // TODO return error + serde_json::from_str::(&user.content.to_string()).ok()?; + let membership = content.membership; // check if this is the room creator joining - if event.prev_event_ids().len() == 1 && membership == MembershipState::Join { + if user.prev_event_ids.len() == 1 && membership == MembershipState::Join { if let Some(create) = auth_events.get(&(EventType::RoomCreate, Some("".into()))) { if let Ok(create_ev) = create.deserialize_content::() { - if event.state_key() == Some(create_ev.creator.to_string()) { + if user.state_key == Some(create_ev.creator.to_string()) { tracing::debug!("m.room.member event allowed via m.room.create"); return Some(true); } @@ -271,16 +275,16 @@ pub fn is_membership_change_allowed( } } - let target_user_id = UserId::try_from(event.state_key().unwrap()).ok().unwrap(); + let target_user_id = UserId::try_from(user.state_key.as_deref().unwrap()) + .ok() + .unwrap(); // if the server_names are different and federation is NOT allowed - if event.room_id().unwrap().server_name() != target_user_id.server_name() - && !can_federate(auth_events) - { + if user.room_id.server_name() != target_user_id.server_name() && !can_federate(auth_events) { tracing::warn!("server cannot federate"); return Some(false); } - let key = (EventType::RoomMember, Some(event.sender().to_string())); + let key = (EventType::RoomMember, Some(user.sender.to_string())); let caller = auth_events.get(&key); let caller_in_room = caller.is_some() && check_membership(caller, MembershipState::Join); @@ -299,12 +303,11 @@ pub fn is_membership_change_allowed( if let Some(jr) = join_rules_event { join_rule = jr .deserialize_content::() - .ok() - .unwrap() // TODO these are errors? and should be treated as a DB failure? + .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 user_level = get_user_power_level(user.sender, auth_events); let target_level = get_user_power_level(&target_user_id, auth_events); // synapse has a not "what to do for default here 50" @@ -321,14 +324,14 @@ pub fn is_membership_change_allowed( "membership": membership, "join_rule": join_rule, "target_user_id": target_user_id, - "event.user_id": event.sender(), + "event.user_id": user.sender, })) .unwrap(), ); if membership == MembershipState::Invite && content.third_party_invite.is_some() { // TODO this is unimpled - if !verify_third_party_invite(event, auth_events) { + if !verify_third_party_invite(&user, auth_events) { tracing::warn!("not invited to this room",); return Some(false); } @@ -341,19 +344,14 @@ pub fn is_membership_change_allowed( } if membership != MembershipState::Join { - if caller_invited - && membership == MembershipState::Leave - && &target_user_id == event.sender() + if caller_invited && membership == MembershipState::Leave && &target_user_id == user.sender { tracing::warn!("join event succeded"); return Some(true); } if !caller_in_room { - tracing::warn!( - "user is not in this room {}", - event.room_id().unwrap().as_str(), - ); + tracing::warn!("user is not in this room {}", user.room_id.as_str(),); return Some(false); // caller is not joined } } @@ -372,7 +370,7 @@ pub fn is_membership_change_allowed( } } } else if membership == MembershipState::Join { - if event.sender() != &target_user_id { + if user.sender != &target_user_id { tracing::warn!("cannot force another user to join"); return Some(false); // cannot force another user to join } else if target_banned { @@ -397,7 +395,7 @@ pub fn is_membership_change_allowed( if target_banned && user_level < ban_level { tracing::warn!("not enough power to unban"); return Some(false); // you cannot unban this user - } else if &target_user_id != event.sender() { + } else if &target_user_id != user.sender { let kick_level = get_named_level(auth_events, "kick", 50); if user_level < kick_level || user_level <= target_level { @@ -734,6 +732,9 @@ pub fn get_send_level( } /// TODO this is unimplemented -pub fn verify_third_party_invite(_event: &StateEvent, _auth_events: &StateMap) -> bool { +pub fn verify_third_party_invite( + _event: &Requester<'_>, + _auth_events: &StateMap, +) -> bool { unimplemented!("impl third party invites") } diff --git a/src/lib.rs b/src/lib.rs index 23571777..ad2f161f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod state_store; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; -pub use state_event::StateEvent; +pub use state_event::{Requester, StateEvent}; pub use state_store::StateStore; // We want to yield to the reactor occasionally during state res when dealing diff --git a/src/state_event.rs b/src/state_event.rs index 34465d14..7b94ae84 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -13,6 +13,14 @@ use serde::{de, Serialize}; use serde_json::value::RawValue as RawJsonValue; use std::time::SystemTime; +pub struct Requester<'a> { + pub prev_event_ids: Vec, + pub room_id: &'a RoomId, + pub content: &'a serde_json::Value, + pub state_key: Option, + pub sender: &'a UserId, +} + #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum StateEvent { @@ -21,6 +29,16 @@ pub enum StateEvent { } impl StateEvent { + pub fn to_requester(&self) -> Requester<'_> { + Requester { + prev_event_ids: self.prev_event_ids(), + room_id: self.room_id().unwrap(), + content: self.content(), + state_key: self.state_key(), + sender: self.sender(), + } + } + pub fn is_power_event(&self) -> bool { match self { Self::Full(any_event) => match any_event { diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 94e35ba3..f590199b 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -28,7 +28,7 @@ static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { - // to activate logging use `RUST_LOG=debug cargo t one_test_only` + // to activate logging use `RUST_LOG=debug cargo t` let _ = LOGGER.call_once(|| { tracer::fmt() .with_env_filter(tracer::EnvFilter::from_default_env()) From d22d83522bdb36abdf2969b52c271a8bbd96c237 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 14 Aug 2020 07:39:30 -0400 Subject: [PATCH 027/130] Make auth_types_for_event take the ruma types instead of StateEvent --- src/event_auth.rs | 17 +++++++++++------ src/lib.rs | 7 ++++++- tests/res_with_auth_ids.rs | 7 ++++++- tests/state_res.rs | 7 ++++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index ace434de..8143fb4d 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -26,19 +26,24 @@ pub enum RedactAllowed { No, } -pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, Option)> { - if event.kind() == EventType::RoomCreate { +pub fn auth_types_for_event( + kind: EventType, + sender: &UserId, + state_key: Option, + content: serde_json::Value, +) -> Vec<(EventType, Option)> { + if kind == EventType::RoomCreate { return vec![]; } let mut auth_types = vec![ (EventType::RoomPowerLevels, Some("".to_string())), - (EventType::RoomMember, Some(event.sender().to_string())), + (EventType::RoomMember, Some(sender.to_string())), (EventType::RoomCreate, Some("".to_string())), ]; - if event.kind() == EventType::RoomMember { - if let Ok(content) = event.deserialize_content::() { + if kind == EventType::RoomMember { + if let Ok(content) = serde_json::from_value::(content) { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { let key = (EventType::RoomJoinRules, Some("".into())); if !auth_types.contains(&key) { @@ -47,7 +52,7 @@ pub fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, Option>, expected_state_ids: state_after.insert((ty, key), event_id.clone()); } - let auth_types = state_res::auth_types_for_event(fake_event); + let auth_types = state_res::auth_types_for_event( + fake_event.kind(), + fake_event.sender(), + fake_event.state_key(), + fake_event.content().clone(), + ); let mut auth_events = vec![]; for key in auth_types { diff --git a/tests/state_res.rs b/tests/state_res.rs index 399133d6..006792dd 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -399,7 +399,12 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: state_after.insert((ty, key), event_id.clone()); } - let auth_types = state_res::auth_types_for_event(fake_event); + let auth_types = state_res::auth_types_for_event( + fake_event.kind(), + fake_event.sender(), + fake_event.state_key(), + fake_event.content().clone(), + ); let mut auth_events = vec![]; for key in auth_types { From ccc75313c54f68ea92763cd6233a26a128a3b4b6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 14 Aug 2020 15:49:43 -0400 Subject: [PATCH 028/130] Add docs and rename _get_event -> get_or_load_event --- src/lib.rs | 67 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f99f21e6..503aff61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,19 @@ pub struct StateResolution; impl StateResolution { /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. + /// + /// ## Arguments + /// + /// * `state_sets` - The incoming state to resolve. Each `StateMap` represents a possible fork + /// in the state of a room. + /// + /// * `event_map` - The `EventMap` acts as a local cache of state, any event that is not found + /// in the `event_map` will be fetched from the `StateStore` and cached in the `event_map`. There + /// is no state kept from separate `resolve` calls, although this could be a potential optimization + /// in the future. + /// + /// * `store` - Any type that implements `StateStore` acts as the database. When an event is not + /// found in the `event_map` it will be retrieved from the `store`. pub fn resolve( &self, room_id: &RoomId, @@ -134,7 +147,7 @@ impl StateResolution { let mut sorted_power_levels = self.reverse_topological_power_sort( room_id, &power_events, - &mut event_map, // TODO use event_map + &mut event_map, store, &all_conflicted, ); @@ -217,7 +230,6 @@ impl StateResolution { // add unconflicted state to the resolved state resolved_state.extend(clean); - // TODO return something not a place holder Ok(ResolutionResult::Resolved(resolved_state)) } @@ -275,7 +287,7 @@ impl StateResolution { (unconflicted_state, conflicted_state) } - /// Returns a Vec of deduped EventIds that appear in some chains but no others. + /// Returns a Vec of deduped EventIds that appear in some chains but not others. pub fn get_auth_chain_diff( &self, room_id: &RoomId, @@ -298,6 +310,12 @@ impl StateResolution { .map_err(Error::TempString) } + /// Events are sorted from "earliest" to "latest". They are compared using + /// the negative power level, the origin server timestamp and incase of a + /// tie the `EventId`s are compared lexicographically. + /// + /// The power level is negative because a higher power level is equated to an + /// earlier (further back in time) origin server timestamp. pub fn reverse_topological_power_sort( &self, room_id: &RoomId, @@ -426,6 +444,7 @@ impl StateResolution { sorted } + /// Find the power level for the sender of `event_id` or return a default value of zero. fn get_power_level_for_sender( &self, room_id: &RoomId, @@ -433,14 +452,15 @@ impl StateResolution { event_map: &mut EventMap, store: &dyn StateStore, ) -> i64 { - tracing::info!("fetch event senders ({}) power level", event_id.to_string()); - let event = self._get_event(room_id, event_id, event_map, store); + tracing::info!("fetch event ({}) senders power level", event_id.to_string()); + + let event = self.get_or_load_event(room_id, event_id, event_map, store); let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? for aid in event.as_ref().unwrap().auth_events() { - if let Some(aev) = self._get_event(room_id, &aid, event_map, store) { + if let Some(aev) = self.get_or_load_event(room_id, &aid, event_map, store) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -492,7 +512,7 @@ impl StateResolution { room_version: &RoomVersionId, power_events: &[EventId], unconflicted_state: &StateMap, - event_map: &mut EventMap, // TODO use event_map over store ?? + event_map: &mut EventMap, store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -509,12 +529,12 @@ impl StateResolution { for (idx, event_id) in power_events.iter().enumerate() { let event = self - ._get_event(room_id, event_id, event_map, store) + .get_or_load_event(room_id, event_id, event_map, store) .unwrap(); let mut auth_events = BTreeMap::new(); for aid in event.auth_events() { - if let Some(ev) = self._get_event(room_id, &aid, event_map, store) { + if let Some(ev) = self.get_or_load_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because for ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); @@ -530,7 +550,7 @@ impl StateResolution { event.content().clone(), ) { if let Some(ev_id) = resolved_state.get(&key) { - if let Some(event) = self._get_event(room_id, ev_id, event_map, store) { + if let Some(event) = self.get_or_load_event(room_id, ev_id, event_map, store) { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); } @@ -588,11 +608,15 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); - let event = self._get_event(room_id, &p, event_map, store).unwrap(); + let event = self + .get_or_load_event(room_id, &p, event_map, store) + .unwrap(); let auth_events = event.auth_events(); pl = None; for aid in auth_events { - let ev = self._get_event(room_id, &aid, event_map, store).unwrap(); + let ev = self + .get_or_load_event(room_id, &aid, event_map, store) + .unwrap(); if ev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; @@ -616,7 +640,7 @@ impl StateResolution { let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - let event = self._get_event(room_id, ev_id, event_map, store); + let event = self.get_or_load_event(room_id, ev_id, event_map, store); let depth = self.get_mainline_depth(room_id, event, &mainline_map, event_map, store); order_map.insert( ev_id, @@ -663,7 +687,9 @@ impl StateResolution { let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { - let aev = self._get_event(room_id, &aid, event_map, store).unwrap(); + let aev = self + .get_or_load_event(room_id, &aid, event_map, store) + .unwrap(); if aev.is_type_and_key(EventType::RoomPowerLevels, "") { event = Some(aev.clone()); break; @@ -691,7 +717,7 @@ impl StateResolution { // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever for aid in self - ._get_event(room_id, &eid, event_map, store) + .get_or_load_event(room_id, &eid, event_map, store) .unwrap() .auth_events() { @@ -707,8 +733,13 @@ impl StateResolution { } } - /// TODO update self if we go that route just as event_map will be updated - fn _get_event( + // TODO having the event_map as a field of self would allow us to keep + // cached state from `resolve` to `resolve` calls, good idea or not? + /// Uses the `event_map` to return the full PDU or fetches from the `StateStore` implementation + /// if the event_map does not have the PDU. + /// + /// If the PDU is missing from the `event_map` it is added. + fn get_or_load_event( &self, room_id: &RoomId, ev_id: &EventId, @@ -729,6 +760,6 @@ impl StateResolution { pub fn is_power_event(event_id: &EventId, event_map: &EventMap) -> bool { match event_map.get(event_id) { Some(state) => state.is_power_event(), - _ => false, // TODO this shouldn't eat errors? + _ => false, } } From 85693cc30ae7b6a0aef5e99089c4acde3be5be06 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 14 Aug 2020 20:29:15 -0400 Subject: [PATCH 029/130] Update signatures since Cargo.lock seemed to be using cached ruma --- src/state_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state_event.rs b/src/state_event.rs index 7b94ae84..b9ab4e40 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -260,7 +260,7 @@ impl StateEvent { } } - pub fn signatures(&self) -> BTreeMap, BTreeMap> { + pub fn signatures(&self) -> BTreeMap, BTreeMap> { match self { Self::Full(ev) => match ev { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, From 789c8140890e076d38b23fa1147c4ff0500c0d38 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 15 Aug 2020 13:32:48 -0400 Subject: [PATCH 030/130] Update ruma to latest --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6aef52eb..d03092ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,8 @@ tracing-subscriber = "0.2.11" # features = ["client-api", "federation-api", "appservice-api"] [dependencies.ruma] -git = "https://github.com/timokoesters/ruma" -branch = "timo-fixes" +git = "https://github.com/ruma/ruma" +rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" features = ["client-api", "federation-api", "appservice-api"] From ce2d5a0d9bb85614e6b65b49239b22bc44643f7d Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 18 Aug 2020 13:46:14 -0400 Subject: [PATCH 031/130] Add license, update readme and add docs for event_auth functions --- LICENSE | 19 +++++++++++++++++++ README.md | 30 ++++++++++++++++-------------- src/event_auth.rs | 20 ++++++++++++++++---- src/lib.rs | 8 +++----- 4 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..09ad525f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Devin Ragotzy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index decc9685..b8eb1bc7 100644 --- a/README.md +++ b/README.md @@ -12,20 +12,24 @@ struct StateEvent { /// A mapping of event type and state_key to some value `T`, usually an `EventId`. pub type StateMap = BTreeMap<(EventType, String), T>; +/// A mapping of `EventId` to `T`, usually a `StateEvent`. +pub type EventMap = BTreeMap; struct StateResolution { - // Should any information be kept or should all of it be fetched from the - // StateStore trait? - event_map: BTreeMap, - - // fields for temp storage during resolution?? - /// The events that conflict and their auth chains. - conflicting_events: StateMap>, + // For now the StateResolution struct is empty. If "caching `event_map` between `resolve` calls + // ends up being more efficient it may have an `event_map` field. } impl StateResolution { - /// The point of this all. Resolve the conflicting set of . - fn resolve(&mut self, events: Vec>) -> StateMap { } + /// The point of this all, resolve the possibly conflicting sets of events. + pub fn resolve( + &self, + room_id: &RoomId, + room_version: &RoomVersionId, + state_sets: &[StateMap], + event_map: Option>, + store: &dyn StateStore, + ) -> Result; } @@ -34,11 +38,9 @@ trait StateStore { /// Return a single event based on the EventId. fn get_event(&self, event_id: &EventId) -> Result; - /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, event_ids: &[EventId]) -> Result, String>; - - /// Returns a Vec of the related auth events to the given `event`. - fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result, String>; + // There are 3 methods that have default implementations `get_events`, `auth_event_ids` and + // `auth_chain_diff`. Each could be overridden if the user has an optimization with their database of + // choice. } ``` diff --git a/src/event_auth.rs b/src/event_auth.rs index 8143fb4d..d6c9e57b 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -26,6 +26,8 @@ pub enum RedactAllowed { No, } +/// For the given event `kind` what are the relevant auth events +/// that are needed to authenticate this `content`. pub fn auth_types_for_event( kind: EventType, sender: &UserId, @@ -71,6 +73,10 @@ pub fn auth_types_for_event( auth_types } +/// Authenticate the incoming `event`. The steps of authentication are: +/// * check that the event is being authenticated for the correct room +/// * check that the events signatures are valid +/// * then there are checks for specific event types pub fn auth_check( room_version: &RoomVersionId, event: &StateEvent, @@ -107,7 +113,7 @@ pub fn auth_check( // TODO do_size_check is false when called by `iterative_auth_check` // do_size_check is also mostly accomplished by ruma with the exception of checking event_type, - // state_key, and json are below a certain size (255 and 65536 respectivly) + // state_key, and json are below a certain size (255 and 65536 respectively) // Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules // @@ -129,7 +135,7 @@ pub fn auth_check( .get("room_version") .cloned() // synapse defaults to version 1 - .unwrap_or(serde_json::json!("1")), + .unwrap_or_else(|| serde_json::json!("1")), ) .is_err() { @@ -446,7 +452,7 @@ pub fn check_event_sender_in_room( ) } -/// Is the user allowed to send a specific event. +/// Is the user allowed to send a specific event based on the rooms power levels. pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); @@ -661,6 +667,8 @@ pub fn check_membership(member_event: Option<&StateEvent>, state: MembershipStat } } +/// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. +/// or return `default` if no power level event is found or zero if no field matches `name`. pub fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { @@ -675,6 +683,8 @@ pub fn get_named_level(auth_events: &StateMap, name: &str, default: } } +/// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` +/// object. pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> i64 { if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { if let Ok(content) = pl.deserialize_content::() @@ -706,6 +716,8 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap } } +/// Helper function to fetch the power level needed to send an event of type +/// `e_type` based on the rooms "m.room.power_level" event. pub fn get_send_level( e_type: EventType, state_key: Option, @@ -720,7 +732,7 @@ pub fn get_send_level( .events .get(&e_type) .cloned() - .unwrap_or(js_int::int!(50)) + .unwrap_or_else(|| js_int::int!(50)) .into(); let state_def: i64 = content.state_default.into(); let event_def: i64 = content.events_default.into(); diff --git a/src/lib.rs b/src/lib.rs index 503aff61..07e9c6d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::or_fun_call)] - use std::{ cmp::Reverse, collections::{BTreeMap, BTreeSet, BinaryHeap}, @@ -266,7 +264,7 @@ impl StateResolution { "SEP {:?}", event_ids .iter() - .map(|i| i.map(ToString::to_string).unwrap_or("None".into())) + .map(|i| i.map(ToString::to_string).unwrap_or_else(|| "None".into())) .collect::>() ); @@ -560,7 +558,7 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().to_string()); if event_auth::auth_check(room_version, &event, auth_events, false) - .ok_or("Auth check failed due to deserialization most likely".to_string()) + .ok_or_else(|| "Auth check failed due to deserialization most likely".to_string()) .map_err(Error::TempString)? { // add event to resolved state map @@ -713,7 +711,7 @@ impl StateResolution { while !state.is_empty() { // we just checked if it was empty so unwrap is fine let eid = state.pop().unwrap(); - graph.entry(eid.clone()).or_insert(vec![]); + graph.entry(eid.clone()).or_insert_with(Vec::new); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever for aid in self From 8650f8fea7adbf03097410f1d10a76dee60bc3c7 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 18 Aug 2020 16:00:13 -0400 Subject: [PATCH 032/130] Add hashes getter to StateEvent --- src/state_event.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/state_event.rs b/src/state_event.rs index b9ab4e40..8c946730 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, convert::TryFrom}; use ruma::{ events::{ from_raw_json_value, - pdu::{Pdu, PduStub}, + pdu::{EventHash, Pdu, PduStub}, room::member::{MemberEventContent, MembershipState}, EventDeHelper, EventType, }, @@ -273,6 +273,19 @@ impl StateEvent { } } + pub fn hashes(&self) -> &EventHash { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.hashes, + Pdu::RoomV3Pdu(ev) => &ev.hashes, + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => &ev.hashes, + PduStub::RoomV3PduStub(ev) => &ev.hashes, + }, + } + } + pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { match self { Self::Full(ev) => match ev { From bafc2016c6258a8f1c984c2eca13680b4ab50e86 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 18 Aug 2020 16:03:24 -0400 Subject: [PATCH 033/130] Add origin getter to StateEvent --- src/state_event.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/state_event.rs b/src/state_event.rs index 8c946730..ae6c1e77 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -201,6 +201,18 @@ impl StateEvent { }, } } + pub fn origin(&self) -> String { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.origin.clone(), + Pdu::RoomV3Pdu(ev) => ev.origin.clone(), + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => ev.origin.clone(), + PduStub::RoomV3PduStub(ev) => ev.origin.clone(), + }, + } + } pub fn prev_event_ids(&self) -> Vec { match self { From 4e9b428c0db50ac3a3421ced12a6fd202a1c36a3 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 18 Aug 2020 16:06:18 -0400 Subject: [PATCH 034/130] Add depth getter to StateEvent --- src/state_event.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/state_event.rs b/src/state_event.rs index ae6c1e77..5830a267 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, convert::TryFrom}; +use js_int::UInt; use ruma::{ events::{ from_raw_json_value, @@ -298,6 +299,19 @@ impl StateEvent { } } + pub fn depth(&self) -> &UInt { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.depth, + Pdu::RoomV3Pdu(ev) => &ev.depth, + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(ev) => &ev.depth, + PduStub::RoomV3PduStub(ev) => &ev.depth, + }, + } + } + pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { match self { Self::Full(ev) => match ev { From 43e0f20d3b032e17e41a47b598f92579552a5bb0 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 20 Aug 2020 17:43:04 -0400 Subject: [PATCH 035/130] Remove self param from all StateResolution methods --- benches/state_res_bench.rs | 15 ++---- src/lib.rs | 95 +++++++++++++++++--------------------- tests/res_with_auth_ids.rs | 15 ++---- tests/state_res.rs | 16 +++---- 4 files changed, 59 insertions(+), 82 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 7f501d47..d9cbb3ab 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -38,25 +38,22 @@ fn lexico_topo_sort(c: &mut Criterion) { event_id("p") => vec![event_id("o")], }; b.iter(|| { - let resolver = StateResolution::default(); - - let _ = - resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())); + let _ = StateResolution::lexicographical_topological_sort(&graph, |id| { + (0, UNIX_EPOCH, id.clone()) + }); }) }); } fn resolution_shallow_auth_chain(c: &mut Criterion) { c.bench_function("resolve state of 5 events one fork", |b| { - let resolver = StateResolution::default(); - let store = TestStore(RefCell::new(btreemap! {})); // build up the DAG let (state_at_bob, state_at_charlie, _) = store.set_up(); b.iter(|| { - let _resolved = match resolver.resolve( + let _resolved = match StateResolution::resolve( &room_id(), &RoomVersionId::Version2, &[state_at_bob.clone(), state_at_charlie.clone()], @@ -73,8 +70,6 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { fn resolve_deeper_event_set(c: &mut Criterion) { c.bench_function("resolve state of 10 events 3 conflicting", |b| { - let resolver = StateResolution::default(); - let init = INITIAL_EVENTS(); let ban = BAN_STATE_SET(); @@ -109,7 +104,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { .collect::>(); b.iter(|| { - let _resolved = match resolver.resolve( + let _resolved = match StateResolution::resolve( &room_id(), &RoomVersionId::Version2, &[state_set_a.clone(), state_set_b.clone()], diff --git a/src/lib.rs b/src/lib.rs index 07e9c6d6..cf01269a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,6 @@ impl StateResolution { /// * `store` - Any type that implements `StateStore` acts as the database. When an event is not /// found in the `event_map` it will be retrieved from the `store`. pub fn resolve( - &self, room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], @@ -73,7 +72,7 @@ impl StateResolution { EventMap::new() }; // split non-conflicting and conflicting state - let (clean, conflicting) = self.separate(&state_sets); + let (clean, conflicting) = StateResolution::separate(&state_sets); tracing::info!("non conflicting {:?}", clean.len()); @@ -85,7 +84,7 @@ impl StateResolution { tracing::info!("{} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks - let mut auth_diff = self.get_auth_chain_diff(room_id, &state_sets, store)?; + let mut auth_diff = StateResolution::get_auth_chain_diff(room_id, &state_sets, store)?; tracing::debug!("auth diff size {}", auth_diff.len()); @@ -142,7 +141,7 @@ impl StateResolution { .collect::>(); // sort the power events based on power_level/clock/event_id and outgoing/incoming edges - let mut sorted_power_levels = self.reverse_topological_power_sort( + let mut sorted_power_levels = StateResolution::reverse_topological_power_sort( room_id, &power_events, &mut event_map, @@ -159,7 +158,7 @@ impl StateResolution { ); // sequentially auth check each power level event event. - let resolved = self.iterative_auth_check( + let resolved = StateResolution::iterative_auth_check( room_id, room_version, &sorted_power_levels, @@ -200,7 +199,7 @@ impl StateResolution { tracing::debug!("PL {:?}", power_event); - let sorted_left_events = self.mainline_sort( + let sorted_left_events = StateResolution::mainline_sort( room_id, &events_to_resolve, power_event, @@ -216,7 +215,7 @@ impl StateResolution { .collect::>() ); - let mut resolved_state = self.iterative_auth_check( + let mut resolved_state = StateResolution::iterative_auth_check( room_id, room_version, &sorted_left_events, @@ -235,7 +234,6 @@ impl StateResolution { /// /// The tuple looks like `(unconflicted, conflicted)`. pub fn separate( - &self, state_sets: &[StateMap], ) -> (StateMap, StateMap>) { use itertools::Itertools; @@ -248,12 +246,7 @@ impl StateResolution { let mut unconflicted_state = StateMap::new(); let mut conflicted_state = StateMap::new(); - for key in state_sets - .iter() - .flat_map(|map| map.keys()) - .dedup() - .collect::>() - { + for key in state_sets.iter().flat_map(|map| map.keys()).dedup() { let mut event_ids = state_sets .iter() .map(|state_set| state_set.get(key)) @@ -287,7 +280,6 @@ impl StateResolution { /// Returns a Vec of deduped EventIds that appear in some chains but not others. pub fn get_auth_chain_diff( - &self, room_id: &RoomId, state_sets: &[StateMap], store: &dyn StateStore, @@ -315,7 +307,6 @@ impl StateResolution { /// The power level is negative because a higher power level is equated to an /// earlier (further back in time) origin server timestamp. pub fn reverse_topological_power_sort( - &self, room_id: &RoomId, events_to_sort: &[EventId], event_map: &mut EventMap, @@ -326,7 +317,7 @@ impl StateResolution { let mut graph = BTreeMap::new(); for (idx, event_id) in events_to_sort.iter().enumerate() { - self.add_event_and_auth_chain_to_graph( + StateResolution::add_event_and_auth_chain_to_graph( room_id, &mut graph, event_id, event_map, store, auth_diff, ); @@ -340,7 +331,8 @@ impl StateResolution { // 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); + let pl = + StateResolution::get_power_level_for_sender(room_id, &event_id, event_map, store); tracing::info!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -352,7 +344,7 @@ impl StateResolution { } } - self.lexicographical_topological_sort(&graph, |event_id| { + StateResolution::lexicographical_topological_sort(&graph, |event_id| { // tracing::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); @@ -376,7 +368,6 @@ impl StateResolution { /// `key_fn` is used as a tie breaker. The tie breaker happens based on /// power level, age, and event_id. pub fn lexicographical_topological_sort( - &self, graph: &BTreeMap>, key_fn: F, ) -> Vec @@ -444,7 +435,6 @@ impl StateResolution { /// Find the power level for the sender of `event_id` or return a default value of zero. fn get_power_level_for_sender( - &self, room_id: &RoomId, event_id: &EventId, event_map: &mut EventMap, @@ -452,13 +442,13 @@ impl StateResolution { ) -> i64 { tracing::info!("fetch event ({}) senders power level", event_id.to_string()); - let event = self.get_or_load_event(room_id, event_id, event_map, store); + let event = StateResolution::get_or_load_event(room_id, event_id, event_map, store); let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? for aid in event.as_ref().unwrap().auth_events() { - if let Some(aev) = self.get_or_load_event(room_id, &aid, event_map, store) { + if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -504,8 +494,12 @@ impl StateResolution { } } - fn iterative_auth_check( - &self, + /// Check the that each event is authenticated based on the events before it. + /// + /// For each `power_events` event we gather the events needed to auth it from the + /// `event_map` or `store` and verify each event using the `event_auth::auth_check` + /// function. + pub fn iterative_auth_check( room_id: &RoomId, room_version: &RoomVersionId, power_events: &[EventId], @@ -526,13 +520,14 @@ impl StateResolution { let mut resolved_state = unconflicted_state.clone(); for (idx, event_id) in power_events.iter().enumerate() { - let event = self - .get_or_load_event(room_id, event_id, event_map, store) - .unwrap(); + let event = + StateResolution::get_or_load_event(room_id, event_id, event_map, store).unwrap(); let mut auth_events = BTreeMap::new(); for aid in event.auth_events() { - if let Some(ev) = self.get_or_load_event(room_id, &aid, event_map, store) { + if let Some(ev) = + StateResolution::get_or_load_event(room_id, &aid, event_map, store) + { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because for ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); @@ -548,7 +543,9 @@ impl StateResolution { event.content().clone(), ) { if let Some(ev_id) = resolved_state.get(&key) { - if let Some(event) = self.get_or_load_event(room_id, ev_id, event_map, store) { + if let Some(event) = + StateResolution::get_or_load_event(room_id, ev_id, event_map, store) + { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); } @@ -582,18 +579,14 @@ impl StateResolution { /// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using /// the `resolved_power_level`. - /// - /// NOTE we rely on the `event_map` beign full at this point. - /// TODO is this ok? - fn mainline_sort( - &self, + pub fn mainline_sort( room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, event_map: &mut EventMap, store: &dyn StateStore, ) -> Vec { - tracing::debug!("mainline sort of remaining events"); + tracing::debug!("mainline sort of events"); // There are no EventId's to sort, bail. if to_sort.is_empty() { @@ -606,15 +599,12 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); - let event = self - .get_or_load_event(room_id, &p, event_map, store) - .unwrap(); + let event = StateResolution::get_or_load_event(room_id, &p, event_map, store).unwrap(); let auth_events = event.auth_events(); pl = None; for aid in auth_events { - let ev = self - .get_or_load_event(room_id, &aid, event_map, store) - .unwrap(); + let ev = + StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); if ev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; @@ -638,8 +628,14 @@ impl StateResolution { let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - let event = self.get_or_load_event(room_id, ev_id, event_map, store); - let depth = self.get_mainline_depth(room_id, event, &mainline_map, event_map, store); + let event = StateResolution::get_or_load_event(room_id, ev_id, event_map, store); + let depth = StateResolution::get_mainline_depth( + room_id, + event, + &mainline_map, + event_map, + store, + ); order_map.insert( ev_id, ( @@ -668,7 +664,6 @@ impl StateResolution { // TODO make `event` not clone every loop fn get_mainline_depth( - &self, room_id: &RoomId, mut event: Option, mainline_map: &EventMap, @@ -685,9 +680,8 @@ impl StateResolution { let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { - let aev = self - .get_or_load_event(room_id, &aid, event_map, store) - .unwrap(); + let aev = + StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); if aev.is_type_and_key(EventType::RoomPowerLevels, "") { event = Some(aev.clone()); break; @@ -699,7 +693,6 @@ impl StateResolution { } fn add_event_and_auth_chain_to_graph( - &self, room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, @@ -714,8 +707,7 @@ impl StateResolution { graph.entry(eid.clone()).or_insert_with(Vec::new); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in self - .get_or_load_event(room_id, &eid, event_map, store) + for aid in StateResolution::get_or_load_event(room_id, &eid, event_map, store) .unwrap() .auth_events() { @@ -738,7 +730,6 @@ impl StateResolution { /// /// If the PDU is missing from the `event_map` it is added. fn get_or_load_event( - &self, room_id: &RoomId, ev_id: &EventId, event_map: &mut EventMap, diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 909ccf4d..4868782e 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -35,8 +35,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let resolver = StateResolution::default(); - let store = TestStore(RefCell::new( INITIAL_EVENTS() .values() @@ -78,7 +76,8 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // resolve the current state and add it to the state_at_event map then continue // on in "time" - for node in resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) + for node in + StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); let event_id = fake_event.event_id(); @@ -107,7 +106,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .collect::>() ); - let resolved = resolver.resolve( + let resolved = StateResolution::resolve( &room_id(), &RoomVersionId::Version1, &state_sets, @@ -589,12 +588,10 @@ fn ban_with_auth_chains() { #[test] fn base_with_auth_chains() { - let resolver = StateResolution::default(); - let store = TestStore(RefCell::new(INITIAL_EVENTS())); let resolved: BTreeMap<_, EventId> = - match resolver.resolve(&room_id(), &RoomVersionId::Version2, &[], None, &store) { + match StateResolution::resolve(&room_id(), &RoomVersionId::Version2, &[], None, &store) { Ok(ResolutionResult::Resolved(state)) => state, Err(e) => panic!("{}", e), _ => panic!("conflicted state left"), @@ -625,8 +622,6 @@ fn base_with_auth_chains() { #[test] fn ban_with_auth_chains2() { - let resolver = StateResolution::default(); - let init = INITIAL_EVENTS(); let ban = BAN_STATE_SET(); @@ -660,7 +655,7 @@ fn ban_with_auth_chains2() { .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) .collect::>(); - let resolved: StateMap = match resolver.resolve( + let resolved: StateMap = match StateResolution::resolve( &room_id(), &RoomVersionId::Version2, &[state_set_a, state_set_b], diff --git a/tests/state_res.rs b/tests/state_res.rs index 006792dd..74f07840 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -295,8 +295,6 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let resolver = StateResolution::default(); - let store = TestStore(RefCell::new( INITIAL_EVENTS() .values() @@ -338,7 +336,8 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // resolve the current state and add it to the state_at_event map then continue // on in "time" - for node in resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) + for node in + StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); let event_id = fake_event.event_id(); @@ -367,7 +366,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .collect::>() ); - let resolved = resolver.resolve( + let resolved = StateResolution::resolve( &room_id(), &RoomVersionId::Version1, &state_sets, @@ -712,14 +711,12 @@ fn topic_setting() { #[test] fn test_event_map_none() { - let resolver = StateResolution::default(); - let store = TestStore(RefCell::new(btreemap! {})); // build up the DAG let (state_at_bob, state_at_charlie, expected) = store.set_up(); - let resolved = match resolver.resolve( + let resolved = match StateResolution::resolve( &room_id(), &RoomVersionId::Version2, &[state_at_bob, state_at_charlie], @@ -736,8 +733,6 @@ fn test_event_map_none() { #[test] fn test_lexicographical_sort() { - let resolver = StateResolution::default(); - let graph = btreemap! { event_id("l") => vec![event_id("o")], event_id("m") => vec![event_id("n"), event_id("o")], @@ -746,7 +741,8 @@ fn test_lexicographical_sort() { event_id("p") => vec![event_id("o")], }; - let res = resolver.lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())); + let res = + StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())); assert_eq!( vec!["o", "l", "n", "m", "p"], From 07807974f79e37b349cc85c95b4ae15543838030 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 21 Aug 2020 07:39:02 -0400 Subject: [PATCH 036/130] Update readme add docs --- README.md | 13 +++++++------ src/lib.rs | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b8eb1bc7..8f7685f2 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,15 @@ pub type StateMap = BTreeMap<(EventType, String), T>; pub type EventMap = BTreeMap; struct StateResolution { - // For now the StateResolution struct is empty. If "caching `event_map` between `resolve` calls - // ends up being more efficient it may have an `event_map` field. + // For now the StateResolution struct is empty. If "caching" `event_map` + // between `resolve` calls nds up being more efficient (probably not as this would eat memory) + // it may have an `event_map` field. The `event_map` is all the event's + // `StateResolution` has to know about in order to resolve state. } impl StateResolution { /// The point of this all, resolve the possibly conflicting sets of events. pub fn resolve( - &self, room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], @@ -38,9 +39,9 @@ trait StateStore { /// Return a single event based on the EventId. fn get_event(&self, event_id: &EventId) -> Result; - // There are 3 methods that have default implementations `get_events`, `auth_event_ids` and - // `auth_chain_diff`. Each could be overridden if the user has an optimization with their database of - // choice. + // There are 3 methods that have default implementations `get_events`, + // `auth_event_ids` and `auth_chain_diff`. Each could be overridden if + // the user has an optimization with their database of choice. } ``` diff --git a/src/lib.rs b/src/lib.rs index cf01269a..1733859c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,8 +301,9 @@ impl StateResolution { } /// Events are sorted from "earliest" to "latest". They are compared using - /// the negative power level, the origin server timestamp and incase of a - /// tie the `EventId`s are compared lexicographically. + /// the negative power level (reverse topological ordering), the + /// origin server timestamp and incase of a tie the `EventId`s + /// are compared lexicographically. /// /// The power level is negative because a higher power level is equated to an /// earlier (further back in time) origin server timestamp. From 63be0b550f518d1ec049c11e5b8f1ee1d365c084 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 21 Aug 2020 17:16:06 -0400 Subject: [PATCH 037/130] Add room version check to event auth and room_version to StateEvent --- Cargo.toml | 3 +-- src/event_auth.rs | 12 +++++++++++- src/lib.rs | 6 ------ src/state_event.rs | 19 ++++++++++++++++++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d03092ac..b2e05bcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,7 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.11" # [dependencies.ruma] -# git = "https://github.com/ruma/ruma" -# rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" +# path = "../__forks__/ruma/ruma" # features = ["client-api", "federation-api", "appservice-api"] [dependencies.ruma] diff --git a/src/event_auth.rs b/src/event_auth.rs index d6c9e57b..ed9e182e 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -105,10 +105,21 @@ pub fn auth_check( false }; + // check the event has been signed by the domain of the sender if event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { tracing::warn!("event not signed by sender's server"); return Some(false); } + + if event.room_version() == RoomVersionId::Version1 + && event + .signatures() + .get(event.event_id().server_name().unwrap()) + .is_none() + { + tracing::warn!("event not signed by event_id's server"); + return Some(false); + } } // TODO do_size_check is false when called by `iterative_auth_check` @@ -128,7 +139,6 @@ pub fn auth_check( } // 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::( event .content() diff --git a/src/lib.rs b/src/lib.rs index 1733859c..0e9b4574 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,12 +352,6 @@ impl StateResolution { tracing::debug!("{:?}", (-*pl, *ev.origin_server_ts(), ev.event_id())); - // count_0.sort_by(|(x, _), (y, _)| { - // x.power_level - // .cmp(&a.power_level) - // .then_with(|| x.origin_server.ts.cmp(&y.origin_server_ts)) - // .then_with(|| x.event_id.cmp(&y.event_id)) - // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. diff --git a/src/state_event.rs b/src/state_event.rs index 5830a267..e15d0531 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -8,7 +8,7 @@ use ruma::{ room::member::{MemberEventContent, MembershipState}, EventDeHelper, EventType, }, - identifiers::{EventId, RoomId, ServerName, UserId}, + EventId, RoomId, RoomVersionId, ServerName, UserId, }; use serde::{de, Serialize}; use serde_json::value::RawValue as RawJsonValue; @@ -332,6 +332,23 @@ impl StateEvent { }, } } + + /// Returns the room version this event is formatted for. + /// + /// Currently either version 1 or 3 is returned, 3 represents + /// version 3 and above. + pub fn room_version(&self) -> RoomVersionId { + match self { + Self::Full(ev) => match ev { + Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version3, + }, + Self::Sync(ev) => match ev { + PduStub::RoomV1PduStub(_) => RoomVersionId::Version1, + PduStub::RoomV3PduStub(_) => RoomVersionId::Version3, + }, + } + } } impl<'de> de::Deserialize<'de> for StateEvent { From e8acae05ff4685290d1b2e9986135b165dbd9d31 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 25 Aug 2020 18:19:18 -0400 Subject: [PATCH 038/130] Add test testing conduits event sorting logic This could possibly be turned into another public function for exporting. A list of ruma::Pdu (events) and another list of ruma::Pdu (auth_events) and returns the sorted list of events. --- Cargo.toml | 1 + src/lib.rs | 8 +- tests/event_sorting.rs | 324 +++++++++++++++++++++++++++++++++++++ tests/res_with_auth_ids.rs | 85 +--------- tests/state_res.rs | 30 ++-- 5 files changed, 352 insertions(+), 96 deletions(-) create mode 100644 tests/event_sorting.rs diff --git a/Cargo.toml b/Cargo.toml index b2e05bcc..779fec77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ features = ["client-api", "federation-api", "appservice-api"] [dev-dependencies] criterion = "0.3.3" +rand = "0.7.3" [[bench]] name = "state_res_bench" diff --git a/src/lib.rs b/src/lib.rs index 0e9b4574..0467ffa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,10 +378,14 @@ impl StateResolution { // https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm // TODO make the BTreeSet conversion cleaner ?? + // outdegree_map is an event referring to the events before it, the + // more outdegree's the more recent the event. let mut outdegree_map: BTreeMap> = graph .iter() .map(|(k, v)| (k.clone(), v.iter().cloned().collect())) .collect(); + + // The number of events that depend on the given event (the eventId key) let mut reverse_graph = BTreeMap::new(); // Vec of nodes that have zero out degree, least recent events. @@ -389,7 +393,7 @@ impl StateResolution { for (node, edges) in graph.iter() { if edges.is_empty() { - // the `Reverse` is because rusts bin heap sorts largest -> smallest we need + // the `Reverse` is because rusts `BinaryHeap` sorts largest -> smallest we need // smallest -> largest zero_outdegree.push(Reverse((key_fn(node), node))); } @@ -407,7 +411,7 @@ impl StateResolution { // we remove the oldest node (most incoming edges) and check against all other let mut sorted = vec![]; - // match out the `Reverse` and take the smallest `node` each time + // destructure the `Reverse` and take the smallest `node` each time while let Some(Reverse((_, node))) = heap.pop() { let node: &EventId = node; for parent in reverse_graph.get(node).unwrap() { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs new file mode 100644 index 00000000..4cd0317d --- /dev/null +++ b/tests/event_sorting.rs @@ -0,0 +1,324 @@ +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom}; + +use ruma::{ + events::{ + pdu::EventHash, + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +use serde_json::{json, Value as JsonValue}; +use state_res::{StateEvent, StateMap, StateStore}; +use tracing_subscriber as tracer; + +use std::sync::Once; + +static LOGGER: Once = Once::new(); + +static mut SERVER_TIMESTAMP: i32 = 0; + +fn event_id(id: &str) -> EventId { + if id.contains('$') { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} + +fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } +} + +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + // this is always called so we can init the logger here + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + to_pdu_event::( + "END", + charlie(), + EventType::RoomMessage, + None, + json!({}), + &[], + &[], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + +fn shuffle(list: &mut [EventId]) { + use rand::Rng; + + let mut rng = rand::thread_rng(); + for i in 1..list.len() { + let j = rng.gen_range(0, list.len()); + list.swap(i, j); + } +} + +fn test_event_sort() { + let mut events = INITIAL_EVENTS(); + + let store = TestStore(RefCell::new(events.clone())); + + let event_map = events + .values() + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .collect::>(); + + let auth_chain = &[] as &[_]; + + let power_events = event_map + .values() + .filter(|pdu| pdu.is_power_event()) + .map(|pdu| pdu.event_id()) + .collect::>(); + + // This is a TODO in conduit + // TODO these events are not guaranteed to be sorted but they are resolved, do + // we need the auth_chain + let sorted_power_events = state_res::StateResolution::reverse_topological_power_sort( + &room_id(), + &power_events, + &mut events, + &store, + &auth_chain, + ); + + // This is a TODO in conduit + // TODO we may be able to skip this since they are resolved according to spec + let resolved_power = state_res::StateResolution::iterative_auth_check( + &room_id(), + &RoomVersionId::Version6, + &sorted_power_events, + &BTreeMap::new(), // unconflicted events + &mut events, + &store, + ) + .expect("iterative auth check failed on resolved events"); + + // don't remove any events so we know it sorts them all correctly + let mut events_to_sort = events.keys().cloned().collect::>(); + + shuffle(&mut events_to_sort); + + let power_level = resolved_power.get(&(EventType::RoomPowerLevels, Some("".into()))); + + let sorted_event_ids = state_res::StateResolution::mainline_sort( + &room_id(), + &events_to_sort, + power_level, + &mut events, + &store, + ); + + assert_eq!( + vec![ + "$CREATE:foo", + "$IMA:foo", + "$IPOWER:foo", + "$IJR:foo", + "$IMB:foo", + "$IMC:foo", + "$END:foo" + ], + sorted_event_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + ) +} + +#[test] +fn test_sort() { + for _ in 0..20 { + // since we shuffle the eventIds before we sort them introducing randomness + // seems like we should test this a few times + test_event_sort() + } +} diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 4868782e..b4dbab52 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -1,12 +1,6 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call)] -use std::{ - cell::RefCell, - collections::{BTreeMap, BTreeSet}, - convert::TryFrom, - sync::Once, - time::UNIX_EPOCH, -}; +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, sync::Once, time::UNIX_EPOCH}; use ruma::{ events::{ @@ -153,7 +147,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } // TODO The event is just remade, adding the auth_events and prev_events here - // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better + // the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; let ev_id = e.event_id(); let event = to_pdu_event( @@ -165,12 +159,9 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: &auth_events, prev_events, ); + // we have to update our store, an actual user of this lib would // be giving us state from a DB. - // - // TODO - // TODO we need to convert the `StateResolution::resolve` to use the event_map - // because the user of this crate cannot update their DB's state. *store.0.borrow_mut().get_mut(&ev_id).unwrap() = event.clone(); state_at_event.insert(node, state_after); @@ -209,17 +200,6 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { - Ok(self - .0 - .borrow() - .iter() - .filter(|e| events.contains(e.0)) - .map(|(_, s)| s) - .cloned() - .collect()) - } - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() @@ -227,65 +207,6 @@ impl StateStore for TestStore { .cloned() .ok_or(format!("{} not found", event_id.to_string())) } - - fn auth_event_ids( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { - let mut result = vec![]; - let mut stack = event_ids.to_vec(); - - // DFS for auth event chain - while !stack.is_empty() { - let ev_id = stack.pop().unwrap(); - if result.contains(&ev_id) { - continue; - } - - result.push(ev_id.clone()); - - let event = self.get_event(room_id, &ev_id).unwrap(); - stack.extend(event.auth_events()); - } - - Ok(result) - } - - fn auth_chain_diff( - &self, - room_id: &RoomId, - event_ids: Vec>, - ) -> Result, String> { - use itertools::Itertools; - - let mut chains = vec![]; - for ids in event_ids { - // TODO state store `auth_event_ids` returns self in the event ids list - // when an event returns `auth_event_ids` self is not contained - let chain = self - .auth_event_ids(room_id, &ids)? - .into_iter() - .collect::>(); - chains.push(chain); - } - - if let Some(chain) = chains.first() { - let rest = chains.iter().skip(1).flatten().cloned().collect(); - let common = chain.intersection(&rest).collect::>(); - - Ok(chains - .iter() - .flatten() - .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() - .collect()) - } else { - Ok(vec![]) - } - } } fn event_id(id: &str) -> EventId { diff --git a/tests/state_res.rs b/tests/state_res.rs index 74f07840..40a89e4b 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,5 +1,3 @@ -#![allow(clippy::or_fun_call, clippy::expect_fun_call)] - use std::{ cell::RefCell, collections::{BTreeMap, BTreeSet}, @@ -317,14 +315,20 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: for pair in INITIAL_EDGES().windows(2) { if let [a, b] = &pair { - graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + graph + .entry(a.clone()) + .or_insert_with(Vec::new) + .push(b.clone()); } } for edge_list in edges { for pair in edge_list.windows(2) { if let [a, b] = &pair { - graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + graph + .entry(a.clone()) + .or_insert_with(Vec::new) + .push(b.clone()); } } } @@ -439,14 +443,16 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: let mut expected_state = StateMap::new(); for node in expected_state_ids { - let ev = event_map.get(&node).expect(&format!( - "{} not found in {:?}", - node.to_string(), - event_map - .keys() - .map(ToString::to_string) - .collect::>(), - )); + let ev = event_map.get(&node).unwrap_or_else(|| { + panic!( + "{} not found in {:?}", + node.to_string(), + event_map + .keys() + .map(ToString::to_string) + .collect::>(), + ) + }); let key = (ev.kind(), ev.state_key()); From 36cec22cf326f6b3e0d8e4283f929ffa964c3fd2 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 26 Aug 2020 10:31:44 -0400 Subject: [PATCH 039/130] Follow spec for is_membership_change_allowed Add checks for caller in room and remove unspec'ed synapse check leave -> join with join_rule = invite --- src/error.rs | 3 + src/event_auth.rs | 167 +++++++++++++++++++++++----------------------- src/lib.rs | 5 +- 3 files changed, 87 insertions(+), 88 deletions(-) diff --git a/src/error.rs b/src/error.rs index 79f91750..4945f975 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,9 @@ pub enum Error { #[error(transparent)] IntParseError(#[from] ParseIntError), + #[error("Not found error: {0}")] + NotFound(String), + // TODO remove once the correct errors are used #[error("an error occured {0}")] TempString(String), diff --git a/src/event_auth.rs b/src/event_auth.rs index ed9e182e..4b071599 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -13,7 +13,7 @@ use serde_json::json; use crate::{ room_version::RoomVersion, state_event::{Requester, StateEvent}, - StateMap, + Result, StateMap, }; /// Represents the 3 event redaction outcomes. @@ -82,14 +82,14 @@ pub fn auth_check( event: &StateEvent, auth_events: StateMap, do_sig_check: bool, -) -> Option { +) -> Result { tracing::info!("auth_check beginning for {}", event.event_id().as_str()); // don't let power from other rooms be used for auth_event in auth_events.values() { if auth_event.room_id() != event.room_id() { tracing::warn!("found auth event that did not match event's room_id"); - return Some(false); + return Ok(false); } } @@ -108,7 +108,7 @@ pub fn auth_check( // check the event has been signed by the domain of the sender if event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { tracing::warn!("event not signed by sender's server"); - return Some(false); + return Ok(false); } if event.room_version() == RoomVersionId::Version1 @@ -118,7 +118,7 @@ pub fn auth_check( .is_none() { tracing::warn!("event not signed by event_id's server"); - return Some(false); + return Ok(false); } } @@ -135,7 +135,7 @@ pub fn auth_check( // domain of room_id must match domain of sender. if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { tracing::warn!("creation events server does not match sender"); - return Some(false); // creation events room id does not match senders + return Ok(false); // creation events room id does not match senders } // if content.room_version is present and is not a valid version @@ -150,11 +150,11 @@ pub fn auth_check( .is_err() { tracing::warn!("invalid room version found in m.room.create event"); - return Some(false); + return Ok(false); } tracing::info!("m.room.create event was allowed"); - return Some(true); + return Ok(true); } // 3. If event does not have m.room.create in auth_events reject. @@ -164,7 +164,7 @@ pub fn auth_check( { tracing::warn!("no m.room.create event in auth chain"); - return Some(false); + return Ok(false); } // check for m.federate @@ -174,7 +174,7 @@ pub fn auth_check( if !can_federate(&auth_events) { tracing::warn!("federation not allowed"); - return Some(false); + return Ok(false); } } @@ -184,41 +184,41 @@ pub fn auth_check( // TODO && room_version "special case aliases auth" ?? if event.state_key().is_none() { tracing::warn!("no state_key field found for event"); - return Some(false); // must have state_key + return Ok(false); // must have state_key } if event.state_key().unwrap().is_empty() { tracing::warn!("state_key must be non-empty"); - return Some(false); // and be non-empty state_key (point to a user_id) + return Ok(false); // and be non-empty state_key (point to a user_id) } if event.state_key() != Some(event.sender().to_string()) { tracing::warn!("no state_key field found for event"); - return Some(false); + return Ok(false); } tracing::info!("m.room.aliases event was allowed"); - return Some(true); + return Ok(true); } if event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); if !is_membership_change_allowed(event.to_requester(), &auth_events)? { - return Some(false); + return Ok(false); } tracing::info!("m.room.member event was allowed"); - return Some(true); + return Ok(true); } - if let Some(in_room) = check_event_sender_in_room(event, &auth_events) { + if let Ok(in_room) = check_event_sender_in_room(event, &auth_events) { if !in_room { tracing::warn!("sender not in room"); - return Some(false); + return Ok(false); } } else { tracing::warn!("sender not in room"); - return Some(false); + return Ok(false); } // Special case to allow m.room.third_party_invite events where ever @@ -230,7 +230,7 @@ pub fn auth_check( if !can_send_event(event, &auth_events)? { tracing::warn!("user cannot send event"); - return Some(false); + return Ok(false); } if event.kind() == EventType::RoomPowerLevels { @@ -238,23 +238,23 @@ pub fn auth_check( if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) { if !required_pwr_lvl { tracing::warn!("power level was not allowed"); - return Some(false); + return Ok(false); } } else { tracing::warn!("power level was not allowed"); - return Some(false); + return Ok(false); } tracing::info!("power levels event allowed"); } if event.kind() == EventType::RoomRedaction { if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? { - return Some(false); + return Ok(false); } } tracing::info!("allowing event passed all checks"); - Some(true) + Ok(true) } // synapse has an `event: &StateEvent` param but it's never used @@ -272,38 +272,35 @@ pub fn can_federate(auth_events: &StateMap) -> bool { } } -/// Dose the user who sent this member event have required power levels to do so. +/// Does the user who sent this member event have required power levels to do so. +/// +/// If called on it's own the following must be true: +/// - there must be a valid state_key in `user` +/// - there must be a membership key in `user.content` i.e. the event is of type "m.room.member" pub fn is_membership_change_allowed( user: Requester<'_>, auth_events: &StateMap, -) -> Option { +) -> Result { let content = // TODO return error - serde_json::from_str::(&user.content.to_string()).ok()?; + serde_json::from_str::(&user.content.to_string())?; let membership = content.membership; - // check if this is the room creator joining + // If the only previous event is an m.room.create and the state_key is the creator, allow if user.prev_event_ids.len() == 1 && membership == MembershipState::Join { if let Some(create) = auth_events.get(&(EventType::RoomCreate, Some("".into()))) { if let Ok(create_ev) = create.deserialize_content::() { if user.state_key == Some(create_ev.creator.to_string()) { tracing::debug!("m.room.member event allowed via m.room.create"); - return Some(true); + return Ok(true); } } } } - let target_user_id = UserId::try_from(user.state_key.as_deref().unwrap()) - .ok() - .unwrap(); - // if the server_names are different and federation is NOT allowed - if user.room_id.server_name() != target_user_id.server_name() && !can_federate(auth_events) { - tracing::warn!("server cannot federate"); - return Some(false); - } + let target_user_id = UserId::try_from(user.state_key.as_deref().unwrap()).unwrap(); let key = (EventType::RoomMember, Some(user.sender.to_string())); let caller = auth_events.get(&key); @@ -323,8 +320,7 @@ pub fn is_membership_change_allowed( let mut join_rule = JoinRule::Invite; if let Some(jr) = join_rules_event { join_rule = jr - .deserialize_content::() - .ok()? // TODO these are errors? and should be treated as a DB failure? + .deserialize_content::()? .join_rule; } @@ -354,77 +350,79 @@ pub fn is_membership_change_allowed( // TODO this is unimpled if !verify_third_party_invite(&user, auth_events) { tracing::warn!("not invited to this room",); - return Some(false); + return Ok(false); } if target_banned { tracing::warn!("banned from this room",); - return Some(false); + return Ok(false); } tracing::info!("invite succeded"); - return Some(true); - } - - if membership != MembershipState::Join { - if caller_invited && membership == MembershipState::Leave && &target_user_id == user.sender - { - tracing::warn!("join event succeded"); - return Some(true); - } - - if !caller_in_room { - tracing::warn!("user is not in this room {}", user.room_id.as_str(),); - return Some(false); // caller is not joined - } + return Ok(true); } if membership == MembershipState::Invite { + if !caller_in_room { + tracing::warn!("invite sender not in room they are inviting user to"); + return Ok(false); + } + if target_banned { tracing::warn!("target has been banned"); - return Some(false); + return Ok(false); } else if target_in_room { tracing::warn!("already in room"); - return Some(false); // already in room + return Ok(false); // already in room } else { let invite_level = get_named_level(auth_events, "invite", 0); if user_level < invite_level { - return Some(false); + return Ok(false); } } } else if membership == MembershipState::Join { if user.sender != &target_user_id { tracing::warn!("cannot force another user to join"); - return Some(false); // cannot force another user to join + return Ok(false); // cannot force another user to join } else if target_banned { tracing::warn!("cannot join when banned"); - return Some(false); // cannot joined when banned + return Ok(false); // cannot joined when banned } else if join_rule == JoinRule::Public { tracing::info!("join rule public") // pass } else if join_rule == JoinRule::Invite { if !caller_in_room && !caller_invited { tracing::warn!("user has not been invited to this room"); - return Some(false); // you are not invited to this room + return Ok(false); // you are not invited to this room } } else { tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix"); // synapse has 2 TODO's may_join list and private rooms // the join_rule is Private or Knock which means it is not yet spec'ed - return Some(false); + return Ok(false); } } else if membership == MembershipState::Leave { + if !caller_in_room { + tracing::warn!("sender not in room they are leaving"); + return Ok(false); + } + if target_banned && user_level < ban_level { tracing::warn!("not enough power to unban"); - return Some(false); // you cannot unban this user + return Ok(false); // you cannot unban this user } else if &target_user_id != user.sender { let kick_level = get_named_level(auth_events, "kick", 50); if user_level < kick_level || user_level <= target_level { tracing::warn!("not enough power to kick user"); - return Some(false); // you do not have the power to kick user + return Ok(false); // you do not have the power to kick user } } } else if membership == MembershipState::Ban { + if !caller_in_room { + tracing::warn!("ban sender not in room they are banning user from"); + return Ok(false); + } + tracing::debug!( "{} < {} || {} <= {}", user_level, @@ -432,17 +430,18 @@ pub fn is_membership_change_allowed( user_level, target_level ); + if user_level < ban_level || user_level <= target_level { tracing::warn!("not enough power to ban"); - return Some(false); + return Ok(false); } } else { tracing::warn!("unknown membership status"); // Unknown membership status - return Some(false); + return Ok(false); } - Some(true) + Ok(true) } /// Is the event's sender in the room that they sent the event to. @@ -451,19 +450,19 @@ pub fn is_membership_change_allowed( pub fn check_event_sender_in_room( event: &StateEvent, auth_events: &StateMap, -) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, Some(event.sender().to_string())))?; +) -> Result { + let mem = auth_events + .get(&(EventType::RoomMember, Some(event.sender().to_string()))) + .ok_or_else(|| crate::Error::NotFound("Authe event was not found".into()))?; // TODO this is check_membership a helper fn in synapse but it does this - Some( - mem.deserialize_content::() - .ok()? - .membership - == MembershipState::Join, - ) + Ok(mem + .deserialize_content::()? + .membership + == MembershipState::Join) } /// Is the user allowed to send a specific event based on the rooms power levels. -pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Option { +pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Result { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); let send_level = get_send_level(event.kind(), event.state_key(), ple); @@ -477,15 +476,15 @@ pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> ); if user_level < send_level { - return Some(false); + return Ok(false); } if let Some(sk) = event.state_key() { if sk.starts_with('@') && sk != event.sender().as_str() { - return Some(false); // permission required to post in this room + return Ok(false); // permission required to post in this room } } - Some(true) + Ok(true) } /// Confirm that the event sender has the required power levels. @@ -637,12 +636,12 @@ pub fn check_redaction( room_version: &RoomVersionId, redaction_event: &StateEvent, auth_events: &StateMap, -) -> Option { +) -> Result { 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); + return Ok(RedactAllowed::CanRedact); } if let RoomVersionId::Version1 = room_version { @@ -650,14 +649,14 @@ pub fn check_redaction( if Some(redaction_event.event_id().server_name()) == redaction_event.redacts().map(|id| id.server_name()) { - return Some(RedactAllowed::OwnEvent); + return Ok(RedactAllowed::OwnEvent); } } else { // TODO synapse has this line also // event.internal_metadata.recheck_redaction = True - return Some(RedactAllowed::OwnEvent); + return Ok(RedactAllowed::OwnEvent); } - Some(RedactAllowed::No) + Ok(RedactAllowed::No) } /// Check that the member event matches `state`. diff --git a/src/lib.rs b/src/lib.rs index 0467ffa6..fe757aed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -553,10 +553,7 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().to_string()); - if event_auth::auth_check(room_version, &event, auth_events, false) - .ok_or_else(|| "Auth check failed due to deserialization most likely".to_string()) - .map_err(Error::TempString)? - { + if event_auth::auth_check(room_version, &event, auth_events, false)? { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); } else { From 025c2df752de4ef7f5e672630dff332c40235ba6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 26 Aug 2020 11:04:30 -0400 Subject: [PATCH 040/130] Allow join room creator only if create event has no prev_events --- src/event_auth.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 4b071599..9e780860 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -292,7 +292,9 @@ pub fn is_membership_change_allowed( if let Some(create) = auth_events.get(&(EventType::RoomCreate, Some("".into()))) { if let Ok(create_ev) = create.deserialize_content::() { - if user.state_key == Some(create_ev.creator.to_string()) { + if user.state_key == Some(create_ev.creator.to_string()) + && create.prev_event_ids().is_empty() + { tracing::debug!("m.room.member event allowed via m.room.create"); return Ok(true); } From fbcd26c6d28dd28072003fc03644c55a0e3312d6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 26 Aug 2020 20:08:48 -0400 Subject: [PATCH 041/130] All of event_auth follows the spec strictly, all the synapse-isms removed --- src/event_auth.rs | 170 +++++++++++++++++++++++++++++----------------- src/lib.rs | 12 +++- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 9e780860..56172480 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -80,6 +80,7 @@ pub fn auth_types_for_event( pub fn auth_check( room_version: &RoomVersionId, event: &StateEvent, + redacted_event: Option<&StateEvent>, auth_events: StateMap, do_sig_check: bool, ) -> Result { @@ -132,19 +133,25 @@ pub fn auth_check( if event.kind() == EventType::RoomCreate { tracing::info!("start m.room.create check"); - // domain of room_id must match domain of sender. + // If it has any previous events, reject + if !event.prev_event_ids().is_empty() { + tracing::warn!("the room creation event had previous events"); + return Ok(false); + } + + // If the domain of the room_id does not match the domain of the sender, reject if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { tracing::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } - // if content.room_version is present and is not a valid version + // If content.room_version is present and is not a recognized version, reject if serde_json::from_value::( event .content() .get("room_version") .cloned() - // synapse defaults to version 1 + // TODO synapse defaults to version 1 .unwrap_or_else(|| serde_json::json!("1")), ) .is_err() @@ -153,11 +160,17 @@ pub fn auth_check( return Ok(false); } + // If content has no creator field, reject + if event.content().get("creator").is_none() { + tracing::warn!("no creator field found in room create content"); + return Ok(false); + } + tracing::info!("m.room.create event was allowed"); return Ok(true); } - // 3. If event does not have m.room.create in auth_events reject. + // 3. If event does not have m.room.create in auth_events reject if auth_events .get(&(EventType::RoomCreate, Some("".into()))) .is_none() @@ -167,16 +180,7 @@ pub fn auth_check( return Ok(false); } - // check for m.federate - if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { - tracing::info!("checking federation"); - - if !can_federate(&auth_events) { - tracing::warn!("federation not allowed"); - - return Ok(false); - } - } + // [synapse] checks for federation here // 4. if type is m.room.aliases if event.kind() == EventType::RoomAliases { @@ -186,13 +190,17 @@ pub fn auth_check( tracing::warn!("no state_key field found for event"); return Ok(false); // must have state_key } - if event.state_key().unwrap().is_empty() { - tracing::warn!("state_key must be non-empty"); - return Ok(false); // and be non-empty state_key (point to a user_id) - } + // TODO this is not part of the spec + // if event.state_key().unwrap().is_empty() { + // tracing::warn!("state_key must be non-empty"); + // return Ok(false); // and be non-empty state_key (point to a user_id) + // } + + // TODO what? "sender's domain doesn't matches" + // If sender's domain doesn't matches state_key, reject if event.state_key() != Some(event.sender().to_string()) { - tracing::warn!("no state_key field found for event"); + tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -203,6 +211,19 @@ pub fn auth_check( if event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); + if event.state_key().is_none() { + tracing::warn!("no state_key found for m.room.member event"); + return Ok(false); + } + + if event + .deserialize_content::() + .is_err() + { + tracing::warn!("no membership filed found for m.room.member event content"); + return Ok(false); + } + if !is_membership_change_allowed(event.to_requester(), &auth_events)? { return Ok(false); } @@ -211,14 +232,17 @@ pub fn auth_check( return Ok(true); } - if let Ok(in_room) = check_event_sender_in_room(event, &auth_events) { - if !in_room { - tracing::warn!("sender not in room"); + // If the sender's current membership state is not join, reject + match check_event_sender_in_room(event, &auth_events) { + Some(true) => {} // sender in room + Some(false) => { + tracing::warn!("sender's membership is not join"); + return Ok(false); + } + None => { + tracing::warn!("sender not found in room"); return Ok(false); } - } else { - tracing::warn!("sender not in room"); - return Ok(false); } // Special case to allow m.room.third_party_invite events where ever @@ -228,6 +252,8 @@ pub fn auth_check( unimplemented!("third party invite") } + // If the event type's required power level is greater than the sender's power level, reject + // If the event has a state_key that starts with an @ and does not match the sender, reject. if !can_send_event(event, &auth_events)? { tracing::warn!("user cannot send event"); return Ok(false); @@ -235,6 +261,7 @@ pub fn auth_check( if event.kind() == EventType::RoomPowerLevels { tracing::info!("starting m.room.power_levels check"); + if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) { if !required_pwr_lvl { tracing::warn!("power level was not allowed"); @@ -248,7 +275,9 @@ pub fn auth_check( } if event.kind() == EventType::RoomRedaction { - if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? { + if let RedactAllowed::No = + check_redaction(room_version, event, redacted_event, &auth_events)? + { return Ok(false); } } @@ -282,7 +311,6 @@ pub fn is_membership_change_allowed( auth_events: &StateMap, ) -> Result { let content = - // TODO return error serde_json::from_str::(&user.content.to_string())?; let membership = content.membership; @@ -326,7 +354,7 @@ pub fn is_membership_change_allowed( .join_rule; } - let user_level = get_user_power_level(user.sender, auth_events); + let senders_level = get_user_power_level(user.sender, auth_events); let target_level = get_user_power_level(&target_user_id, auth_events); // synapse has a not "what to do for default here 50" @@ -363,11 +391,13 @@ pub fn is_membership_change_allowed( } if membership == MembershipState::Invite { + // if senders current membership is not join reject if !caller_in_room { tracing::warn!("invite sender not in room they are inviting user to"); return Ok(false); } + // If the targets current membership is ban or join if target_banned { tracing::warn!("target has been banned"); return Ok(false); @@ -376,11 +406,15 @@ pub fn is_membership_change_allowed( return Ok(false); // already in room } else { let invite_level = get_named_level(auth_events, "invite", 0); - if user_level < invite_level { + // If the sender's power level is greater than or equal to the invite level, allow. + if senders_level < invite_level { + tracing::warn!("invite sender does not have power to invite"); return Ok(false); } } + // we already check if the join event was the room creator } else if membership == MembershipState::Join { + // If the sender does not match state_key, reject. if user.sender != &target_user_id { tracing::warn!("cannot force another user to join"); return Ok(false); // cannot force another user to join @@ -403,23 +437,28 @@ pub fn is_membership_change_allowed( return Ok(false); } } else if membership == MembershipState::Leave { + if user.sender == &target_user_id && !(caller_in_room || caller_invited) {} + // if senders current membership is not join reject if !caller_in_room { tracing::warn!("sender not in room they are leaving"); return Ok(false); } - if target_banned && user_level < ban_level { + // If the target user's current membership state is ban, and the sender's power level is less than the ban level, reject + if target_banned && senders_level < ban_level { tracing::warn!("not enough power to unban"); return Ok(false); // you cannot unban this user - } else if &target_user_id != user.sender { - let kick_level = get_named_level(auth_events, "kick", 50); - if user_level < kick_level || user_level <= target_level { - tracing::warn!("not enough power to kick user"); - return Ok(false); // you do not have the power to kick user - } + // If the sender's power level is greater than or equal to the kick level, + // and the target user's power level is less than the sender's power level, allow + } else if senders_level <= get_named_level(auth_events, "kick", 50) + || target_level < senders_level + { + tracing::warn!("not enough power to kick user"); + return Ok(false); // you do not have the power to kick user } } else if membership == MembershipState::Ban { + // if senders current membership is not join reject if !caller_in_room { tracing::warn!("ban sender not in room they are banning user from"); return Ok(false); @@ -427,13 +466,13 @@ pub fn is_membership_change_allowed( tracing::debug!( "{} < {} || {} <= {}", - user_level, + senders_level, ban_level, - user_level, + senders_level, target_level ); - if user_level < ban_level || user_level <= target_level { + if senders_level < ban_level || senders_level <= target_level { tracing::warn!("not enough power to ban"); return Ok(false); } @@ -447,37 +486,36 @@ pub fn is_membership_change_allowed( } /// Is the event's sender in the room that they sent the event to. -/// -/// A return value of None is not a failure pub fn check_event_sender_in_room( event: &StateEvent, auth_events: &StateMap, -) -> Result { - let mem = auth_events - .get(&(EventType::RoomMember, Some(event.sender().to_string()))) - .ok_or_else(|| crate::Error::NotFound("Authe event was not found".into()))?; +) -> Option { + let mem = auth_events.get(&(EventType::RoomMember, Some(event.sender().to_string())))?; // TODO this is check_membership a helper fn in synapse but it does this - Ok(mem - .deserialize_content::()? - .membership - == MembershipState::Join) + Some( + mem.deserialize_content::() + .ok()? + .membership + == MembershipState::Join, + ) } -/// Is the user allowed to send a specific event based on the rooms power levels. +/// Is the user allowed to send a specific event based on the rooms power levels. Does the event +/// have the correct userId as it's state_key if it's not the "" state_key. pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Result { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); - let send_level = get_send_level(event.kind(), event.state_key(), ple); + let event_type_power_level = get_send_level(event.kind(), event.state_key(), ple); let user_level = get_user_power_level(event.sender(), auth_events); tracing::debug!( - "{} snd {} usr {}", + "{} ev_type {} usr {}", event.event_id().to_string(), - send_level, + event_type_power_level, user_level ); - if user_level < send_level { + if user_level < event_type_power_level { return Ok(false); } @@ -495,17 +533,17 @@ pub fn check_power_levels( power_event: &StateEvent, auth_events: &StateMap, ) -> Option { - use itertools::Itertools; - let key = (power_event.kind(), power_event.state_key()); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { - // TODO synapse returns here, shouldn't this be an error ?? + // If there is no previous m.room.power_levels event in the room, allow return Some(true); }; + // If users key in content is not a dictionary with keys that are valid user IDs + // with values that are integers (or a string that is an integer), reject. let user_content = power_event .deserialize_content::() .unwrap(); @@ -521,7 +559,7 @@ pub fn check_power_levels( let mut user_levels_to_check = btreeset![]; let old_list = ¤t_content.users; let user_list = &user_content.users; - for user in old_list.keys().chain(user_list.keys()).dedup() { + for user in old_list.keys().chain(user_list.keys()) { let user: &UserId = user; user_levels_to_check.insert(user); } @@ -531,7 +569,7 @@ pub fn check_power_levels( let mut event_levels_to_check = btreeset![]; let old_list = ¤t_content.events; let new_list = &user_content.events; - for ev_id in old_list.keys().chain(new_list.keys()).dedup() { + for ev_id in old_list.keys().chain(new_list.keys()) { let ev_id: &EventType = ev_id; event_levels_to_check.insert(ev_id); } @@ -637,27 +675,31 @@ fn get_deserialize_levels( pub fn check_redaction( room_version: &RoomVersionId, redaction_event: &StateEvent, + redacted_event: Option<&StateEvent>, auth_events: &StateMap, ) -> Result { 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 { + tracing::info!("redaction allowed via power levels"); return Ok(RedactAllowed::CanRedact); } if let RoomVersionId::Version1 = room_version { // are the redacter and redactee in the same domain - if Some(redaction_event.event_id().server_name()) - == redaction_event.redacts().map(|id| id.server_name()) + if Some(redaction_event.sender().server_name()) + == redaction_event.redacts().and_then(|id| id.server_name()) { + tracing::info!("redaction event allowed via room version 1 rules"); return Ok(RedactAllowed::OwnEvent); } - } else { - // TODO synapse has this line also - // event.internal_metadata.recheck_redaction = True + // redactions to events where the sender's domains match, allow + } else if redacted_event.map(|ev| ev.sender()) == Some(redaction_event.sender()) { + tracing::info!("redaction allowed via own redaction"); return Ok(RedactAllowed::OwnEvent); } + Ok(RedactAllowed::No) } diff --git a/src/lib.rs b/src/lib.rs index fe757aed..a7b09469 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -553,7 +553,17 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().to_string()); - if event_auth::auth_check(room_version, &event, auth_events, false)? { + let redacted_event = event + .redacts() + .and_then(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)); + + if event_auth::auth_check( + room_version, + &event, + redacted_event.as_ref(), + auth_events, + false, + )? { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); } else { From 17958665f6592af3ef478024fd1d75c384a30e7f Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 26 Aug 2020 20:51:39 -0400 Subject: [PATCH 042/130] Update docs in event_auth and add first few event_auth tests --- src/event_auth.rs | 5 - tests/event_auth.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 5 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 56172480..e0fa6d0c 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -286,7 +286,6 @@ pub fn auth_check( Ok(true) } -// synapse has an `event: &StateEvent` param but it's never used /// Can this room federate based on its m.room.create event. pub fn can_federate(auth_events: &StateMap) -> bool { let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); @@ -302,10 +301,6 @@ pub fn can_federate(auth_events: &StateMap) -> bool { } /// Does the user who sent this member event have required power levels to do so. -/// -/// If called on it's own the following must be true: -/// - there must be a valid state_key in `user` -/// - there must be a membership key in `user.content` i.e. the event is of type "m.room.member" pub fn is_membership_change_allowed( user: Requester<'_>, auth_events: &StateMap, diff --git a/tests/event_auth.rs b/tests/event_auth.rs index e69de29b..f9d0c752 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -0,0 +1,280 @@ +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom}; + +use ruma::{ + events::{ + pdu::EventHash, + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +use serde_json::{json, Value as JsonValue}; +use state_res::{ + event_auth::{ + auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, + is_membership_change_allowed, + }, + Requester, StateEvent, StateMap, StateStore, +}; +use tracing_subscriber as tracer; + +use std::sync::Once; + +static LOGGER: Once = Once::new(); + +static mut SERVER_TIMESTAMP: i32 = 0; + +fn event_id(id: &str) -> EventId { + if id.contains('$') { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} + +fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } +} + +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + // this is always called so we can init the logger here + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + +#[test] +fn test_ban_pass() { + let events = INITIAL_EVENTS(); + + let auth_events = events + .values() + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .collect::>(); + + let requester = Requester { + prev_event_ids: vec![event_id("IMC")], + room_id: &room_id(), + content: &member_content_ban(), + state_key: Some(charlie().to_string()), + sender: &alice(), + }; + + assert!(is_membership_change_allowed(requester, &auth_events).unwrap()) +} + +#[test] +fn test_ban_fail() { + let events = INITIAL_EVENTS(); + + let auth_events = events + .values() + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .collect::>(); + + let requester = Requester { + prev_event_ids: vec![event_id("IMC")], + room_id: &room_id(), + content: &member_content_ban(), + state_key: Some(alice().to_string()), + sender: &charlie(), + }; + + assert!(!is_membership_change_allowed(requester, &auth_events).unwrap()) +} From aadccdee645d40610d443c2bf5098ad19bdabe63 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 27 Aug 2020 09:08:52 -0400 Subject: [PATCH 043/130] Fix DM room creator rejoining Check only the previous event is a RoomCreate event not that one exists --- src/event_auth.rs | 69 ++++++++++++++++++++------------------------- src/lib.rs | 10 ++++--- tests/event_auth.rs | 17 ++++++++--- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index e0fa6d0c..0f5f2cb3 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -80,7 +80,7 @@ pub fn auth_types_for_event( pub fn auth_check( room_version: &RoomVersionId, event: &StateEvent, - redacted_event: Option<&StateEvent>, + prev_event: Option<&StateEvent>, auth_events: StateMap, do_sig_check: bool, ) -> Result { @@ -197,9 +197,8 @@ pub fn auth_check( // return Ok(false); // and be non-empty state_key (point to a user_id) // } - // TODO what? "sender's domain doesn't matches" // If sender's domain doesn't matches state_key, reject - if event.state_key() != Some(event.sender().to_string()) { + if event.state_key().as_deref() != Some(event.sender().server_name().as_str()) { tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -224,7 +223,7 @@ pub fn auth_check( return Ok(false); } - if !is_membership_change_allowed(event.to_requester(), &auth_events)? { + if !is_membership_change_allowed(event.to_requester(), prev_event, &auth_events)? { return Ok(false); } @@ -275,9 +274,7 @@ pub fn auth_check( } if event.kind() == EventType::RoomRedaction { - if let RedactAllowed::No = - check_redaction(room_version, event, redacted_event, &auth_events)? - { + if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? { return Ok(false); } } @@ -303,6 +300,7 @@ pub fn can_federate(auth_events: &StateMap) -> bool { /// Does the user who sent this member event have required power levels to do so. pub fn is_membership_change_allowed( user: Requester<'_>, + prev_event: Option<&StateEvent>, auth_events: &StateMap, ) -> Result { let content = @@ -312,7 +310,7 @@ pub fn is_membership_change_allowed( // If the only previous event is an m.room.create and the state_key is the creator, allow if user.prev_event_ids.len() == 1 && membership == MembershipState::Join { - if let Some(create) = auth_events.get(&(EventType::RoomCreate, Some("".into()))) { + if let Some(create) = prev_event { if let Ok(create_ev) = create.deserialize_content::() { if user.state_key == Some(create_ev.creator.to_string()) @@ -371,6 +369,7 @@ pub fn is_membership_change_allowed( .unwrap(), ); + // we already check if the join event was the room creator if membership == MembershipState::Invite && content.third_party_invite.is_some() { // TODO this is unimpled if !verify_third_party_invite(&user, auth_events) { @@ -383,9 +382,30 @@ pub fn is_membership_change_allowed( } tracing::info!("invite succeded"); return Ok(true); - } + } else if membership == MembershipState::Join { + // If the sender does not match state_key, reject. + if user.sender != &target_user_id { + tracing::warn!("cannot force another user to join"); + return Ok(false); // cannot force another user to join + } else if target_banned { + tracing::warn!("cannot join when banned"); + return Ok(false); // cannot joined when banned + } else if join_rule == JoinRule::Invite { + if !caller_in_room && !caller_invited { + tracing::warn!("user has not been invited to this room"); + return Ok(false); // you are not invited to this room + } + } else if join_rule == JoinRule::Public { + tracing::info!("join rule public") + // pass + } else { + tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix"); + // synapse has 2 TODO's may_join list and private rooms - if membership == MembershipState::Invite { + // the join_rule is Private or Knock which means it is not yet spec'ed + return Ok(false); + } + } else if membership == MembershipState::Invite { // if senders current membership is not join reject if !caller_in_room { tracing::warn!("invite sender not in room they are inviting user to"); @@ -407,30 +427,6 @@ pub fn is_membership_change_allowed( return Ok(false); } } - // we already check if the join event was the room creator - } else if membership == MembershipState::Join { - // If the sender does not match state_key, reject. - if user.sender != &target_user_id { - tracing::warn!("cannot force another user to join"); - return Ok(false); // cannot force another user to join - } else if target_banned { - tracing::warn!("cannot join when banned"); - return Ok(false); // cannot joined when banned - } else if join_rule == JoinRule::Public { - tracing::info!("join rule public") - // pass - } else if join_rule == JoinRule::Invite { - if !caller_in_room && !caller_invited { - tracing::warn!("user has not been invited to this room"); - return Ok(false); // you are not invited to this room - } - } else { - tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix"); - // synapse has 2 TODO's may_join list and private rooms - - // the join_rule is Private or Knock which means it is not yet spec'ed - return Ok(false); - } } else if membership == MembershipState::Leave { if user.sender == &target_user_id && !(caller_in_room || caller_invited) {} // if senders current membership is not join reject @@ -670,7 +666,6 @@ fn get_deserialize_levels( pub fn check_redaction( room_version: &RoomVersionId, redaction_event: &StateEvent, - redacted_event: Option<&StateEvent>, auth_events: &StateMap, ) -> Result { let user_level = get_user_power_level(redaction_event.sender(), auth_events); @@ -689,10 +684,6 @@ pub fn check_redaction( tracing::info!("redaction event allowed via room version 1 rules"); return Ok(RedactAllowed::OwnEvent); } - // redactions to events where the sender's domains match, allow - } else if redacted_event.map(|ev| ev.sender()) == Some(redaction_event.sender()) { - tracing::info!("redaction allowed via own redaction"); - return Ok(RedactAllowed::OwnEvent); } Ok(RedactAllowed::No) diff --git a/src/lib.rs b/src/lib.rs index a7b09469..42c9d197 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -553,14 +553,16 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().to_string()); - let redacted_event = event - .redacts() - .and_then(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)); + let most_recent_prev_event = event + .prev_event_ids() + .iter() + .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) + .next_back(); if event_auth::auth_check( room_version, &event, - redacted_event.as_ref(), + most_recent_prev_event.as_ref(), auth_events, false, )? { diff --git a/tests/event_auth.rs b/tests/event_auth.rs index f9d0c752..7ffa58e7 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -9,12 +9,13 @@ use ruma::{ }, EventType, }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + identifiers::{EventId, RoomId, UserId}, }; use serde_json::{json, Value as JsonValue}; +#[rustfmt::skip] // this deletes the comments for some reason yay! use state_res::{ event_auth::{ - auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, + // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, is_membership_change_allowed, }, Requester, StateEvent, StateMap, StateStore, @@ -243,6 +244,10 @@ fn INITIAL_EVENTS() -> BTreeMap { fn test_ban_pass() { let events = INITIAL_EVENTS(); + let prev = events + .values() + .find(|ev| ev.event_id().as_str().contains("IMC")); + let auth_events = events .values() .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) @@ -256,13 +261,17 @@ fn test_ban_pass() { sender: &alice(), }; - assert!(is_membership_change_allowed(requester, &auth_events).unwrap()) + assert!(is_membership_change_allowed(requester, prev, &auth_events).unwrap()) } #[test] fn test_ban_fail() { let events = INITIAL_EVENTS(); + let prev = events + .values() + .find(|ev| ev.event_id().as_str().contains("IMC")); + let auth_events = events .values() .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) @@ -276,5 +285,5 @@ fn test_ban_fail() { sender: &charlie(), }; - assert!(!is_membership_change_allowed(requester, &auth_events).unwrap()) + assert!(!is_membership_change_allowed(requester, prev, &auth_events).unwrap()) } From b846aec94a0efd22dc25e2cb88e35d8efa330892 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 27 Aug 2020 15:46:36 -0400 Subject: [PATCH 044/130] Replace membership auth with timo's logic --- src/event_auth.rs | 300 +++++++++++++++++++++----------------------- tests/event_auth.rs | 6 +- 2 files changed, 146 insertions(+), 160 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 0f5f2cb3..a904fb4d 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -1,19 +1,23 @@ -use std::convert::TryFrom; +use std::{collections::BTreeMap, convert::TryFrom}; use maplit::btreeset; use ruma::{ events::{ - room::{self, join_rules::JoinRule, member::MembershipState}, + room::{ + self, + join_rules::JoinRule, + member::{self, MembershipState}, + power_levels::{self, PowerLevelsEventContent}, + }, EventType, }, identifiers::{RoomVersionId, UserId}, }; -use serde_json::json; use crate::{ room_version::RoomVersion, state_event::{Requester, StateEvent}, - Result, StateMap, + Error, Result, StateMap, }; /// Represents the 3 event redaction outcomes. @@ -223,7 +227,7 @@ pub fn auth_check( return Ok(false); } - if !is_membership_change_allowed(event.to_requester(), prev_event, &auth_events)? { + if !valid_membership_change(event.to_requester(), prev_event, &auth_events)? { return Ok(false); } @@ -298,182 +302,164 @@ pub fn can_federate(auth_events: &StateMap) -> bool { } /// Does the user who sent this member event have required power levels to do so. -pub fn is_membership_change_allowed( +/// +/// * `user` - Information about the membership event and user making the request. +/// * `prev_event` - The event that occurred immediately before the `user` event or None. +/// * `auth_events` - The set of auth events that relate to a membership event. +/// this is generated by calling `auth_types_for_event` with the membership event and +/// the current State. +pub fn valid_membership_change( user: Requester<'_>, prev_event: Option<&StateEvent>, auth_events: &StateMap, ) -> Result { + let state_key = if let Some(s) = user.state_key.as_ref() { + s + } else { + return Err(Error::TempString("State event requires state_key".into())); + }; + let content = serde_json::from_str::(&user.content.to_string())?; - let membership = content.membership; + let target_membership = content.membership; - // If the only previous event is an m.room.create and the state_key is the creator, allow - if user.prev_event_ids.len() == 1 && membership == MembershipState::Join { - if let Some(create) = prev_event { - if let Ok(create_ev) = create.deserialize_content::() - { - if user.state_key == Some(create_ev.creator.to_string()) - && create.prev_event_ids().is_empty() - { - tracing::debug!("m.room.member event allowed via m.room.create"); - return Ok(true); - } - } - } - } - - let target_user_id = UserId::try_from(user.state_key.as_deref().unwrap()).unwrap(); + let target_user_id = + UserId::try_from(state_key.as_str()).map_err(|e| Error::TempString(format!("{}", e)))?; let key = (EventType::RoomMember, Some(user.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 sender = auth_events.get(&key); + let sender_membership = + sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { + Ok(pdu + .deserialize_content::()? + .membership) + })?; let key = (EventType::RoomMember, Some(target_user_id.to_string())); - let target = auth_events.get(&key); + let current = auth_events.get(&key); + let current_membership = + current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { + Ok(pdu + .deserialize_content::()? + .membership) + })?; - 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::RoomPowerLevels, Some("".into())); + let power_levels = auth_events.get(&key).map_or_else( + || { + Ok::<_, Error>(power_levels::PowerLevelsEventContent { + ban: 50.into(), + events: BTreeMap::new(), + events_default: 0.into(), + invite: 50.into(), + kick: 50.into(), + redact: 50.into(), + state_default: 0.into(), + users: BTreeMap::new(), + users_default: 0.into(), + notifications: ruma::events::room::power_levels::NotificationPowerLevels { + room: 50.into(), + }, + }) + }, + |power_levels| { + power_levels + .deserialize_content::() + .map_err(Into::into) + }, + )?; + + let sender_power = power_levels.users.get(&user.sender).map_or_else( + || { + if sender_membership != member::MembershipState::Join { + None + } else { + Some(&power_levels.users_default) + } + }, + // If it's okay, wrap with Some(_) + Some, + ); + let target_power = power_levels.users.get(&target_user_id).map_or_else( + || { + if target_membership != member::MembershipState::Join { + None + } else { + Some(&power_levels.users_default) + } + }, + // If it's okay, wrap with Some(_) + Some, + ); let key = (EventType::RoomJoinRules, Some("".to_string())); let join_rules_event = auth_events.get(&key); - - let mut join_rule = JoinRule::Invite; + let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { - join_rule = jr + join_rules = jr .deserialize_content::()? .join_rule; } - let senders_level = get_user_power_level(user.sender, auth_events); - let target_level = get_user_power_level(&target_user_id, auth_events); - - // synapse has a not "what to do for default here 50" - let ban_level = get_named_level(auth_events, "ban", 50); - - // TODO clean this up - tracing::debug!( - "_is_membership_change_allowed: {}", - serde_json::to_string_pretty(&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": user.sender, - })) - .unwrap(), - ); - - // we already check if the join event was the room creator - if membership == MembershipState::Invite && content.third_party_invite.is_some() { - // TODO this is unimpled - if !verify_third_party_invite(&user, auth_events) { - tracing::warn!("not invited to this room",); - return Ok(false); + if let Some(prev) = prev_event { + if prev.kind() == EventType::RoomCreate && prev.prev_event_ids().is_empty() { + return Ok(true); } - if target_banned { - tracing::warn!("banned from this room",); - return Ok(false); - } - tracing::info!("invite succeded"); - return Ok(true); - } else if membership == MembershipState::Join { - // If the sender does not match state_key, reject. - if user.sender != &target_user_id { - tracing::warn!("cannot force another user to join"); - return Ok(false); // cannot force another user to join - } else if target_banned { - tracing::warn!("cannot join when banned"); - return Ok(false); // cannot joined when banned - } else if join_rule == JoinRule::Invite { - if !caller_in_room && !caller_invited { - tracing::warn!("user has not been invited to this room"); - return Ok(false); // you are not invited to this room - } - } else if join_rule == JoinRule::Public { - tracing::info!("join rule public") - // pass - } else { - tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix"); - // synapse has 2 TODO's may_join list and private rooms - - // the join_rule is Private or Knock which means it is not yet spec'ed - return Ok(false); - } - } else if membership == MembershipState::Invite { - // if senders current membership is not join reject - if !caller_in_room { - tracing::warn!("invite sender not in room they are inviting user to"); - return Ok(false); - } - - // If the targets current membership is ban or join - if target_banned { - tracing::warn!("target has been banned"); - return Ok(false); - } else if target_in_room { - tracing::warn!("already in room"); - return Ok(false); // already in room - } else { - let invite_level = get_named_level(auth_events, "invite", 0); - // If the sender's power level is greater than or equal to the invite level, allow. - if senders_level < invite_level { - tracing::warn!("invite sender does not have power to invite"); - return Ok(false); - } - } - } else if membership == MembershipState::Leave { - if user.sender == &target_user_id && !(caller_in_room || caller_invited) {} - // if senders current membership is not join reject - if !caller_in_room { - tracing::warn!("sender not in room they are leaving"); - return Ok(false); - } - - // If the target user's current membership state is ban, and the sender's power level is less than the ban level, reject - if target_banned && senders_level < ban_level { - tracing::warn!("not enough power to unban"); - return Ok(false); // you cannot unban this user - - // If the sender's power level is greater than or equal to the kick level, - // and the target user's power level is less than the sender's power level, allow - } else if senders_level <= get_named_level(auth_events, "kick", 50) - || target_level < senders_level - { - tracing::warn!("not enough power to kick user"); - return Ok(false); // you do not have the power to kick user - } - } else if membership == MembershipState::Ban { - // if senders current membership is not join reject - if !caller_in_room { - tracing::warn!("ban sender not in room they are banning user from"); - return Ok(false); - } - - tracing::debug!( - "{} < {} || {} <= {}", - senders_level, - ban_level, - senders_level, - target_level - ); - - if senders_level < ban_level || senders_level <= target_level { - tracing::warn!("not enough power to ban"); - return Ok(false); - } - } else { - tracing::warn!("unknown membership status"); - // Unknown membership status - return Ok(false); } - Ok(true) + Ok(if target_membership == MembershipState::Join { + if user.sender != &target_user_id { + false + } else if let MembershipState::Ban = current_membership { + false + } else { + join_rules == JoinRule::Invite + && (current_membership == MembershipState::Join + || current_membership == MembershipState::Invite) + || join_rules == JoinRule::Public + } + } else if target_membership == MembershipState::Invite { + if let Some(_tp_id) = content.third_party_invite { + if current_membership == MembershipState::Ban { + false + } else { + // TODO this is not filled out + verify_third_party_invite(&user, auth_events) + } + } else if sender_membership != MembershipState::Join + || current_membership == MembershipState::Join + || current_membership == MembershipState::Ban + { + false + } else { + sender_power + .filter(|&p| p >= &power_levels.invite) + .is_some() + } + } else if target_membership == MembershipState::Leave { + if user.sender == &target_user_id { + current_membership == MembershipState::Join + || current_membership == MembershipState::Invite + } else if sender_membership != MembershipState::Join + || current_membership == MembershipState::Ban + && sender_power.filter(|&p| p < &power_levels.ban).is_some() + { + false + } else { + sender_power.filter(|&p| p >= &power_levels.kick).is_some() + && target_power < sender_power + } + } else if target_membership == MembershipState::Ban { + if sender_membership != MembershipState::Join { + false + } else { + sender_power.filter(|&p| p >= &power_levels.ban).is_some() + && target_power < sender_power + } + } else { + false + }) } /// Is the event's sender in the room that they sent the event to. diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 7ffa58e7..6ae41778 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -16,7 +16,7 @@ use serde_json::{json, Value as JsonValue}; use state_res::{ event_auth::{ // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, - is_membership_change_allowed, + valid_membership_change, }, Requester, StateEvent, StateMap, StateStore, }; @@ -261,7 +261,7 @@ fn test_ban_pass() { sender: &alice(), }; - assert!(is_membership_change_allowed(requester, prev, &auth_events).unwrap()) + assert!(valid_membership_change(requester, prev, &auth_events).unwrap()) } #[test] @@ -285,5 +285,5 @@ fn test_ban_fail() { sender: &charlie(), }; - assert!(!is_membership_change_allowed(requester, prev, &auth_events).unwrap()) + assert!(!valid_membership_change(requester, prev, &auth_events).unwrap()) } From 394d26744a6586ccdc01838964bb27dab289eee5 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 27 Aug 2020 19:32:32 -0400 Subject: [PATCH 045/130] Use own Error type for all errors --- benches/state_res_bench.rs | 85 +++---------------------------- src/error.rs | 17 +++++-- src/event_auth.rs | 102 ++++++++++++++++++++----------------- src/lib.rs | 20 ++++---- src/state_store.rs | 18 ++----- tests/event_auth.rs | 6 +-- tests/event_sorting.rs | 6 +-- tests/res_with_auth_ids.rs | 8 +-- tests/state_res.rs | 86 +++---------------------------- 9 files changed, 105 insertions(+), 243 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index d9cbb3ab..369750c6 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -3,12 +3,7 @@ // `cargo bench unknown option --save-baseline`. // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. -use std::{ - cell::RefCell, - collections::{BTreeMap, BTreeSet}, - convert::TryFrom, - time::UNIX_EPOCH, -}; +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH}; use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; @@ -24,7 +19,9 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; +use state_res::{ + Error, ResolutionResult, Result, StateEvent, StateMap, StateResolution, StateStore, +}; static mut SERVER_TIMESTAMP: i32 = 0; @@ -137,82 +134,12 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { - Ok(self - .0 - .borrow() - .iter() - .filter(|e| events.contains(e.0)) - .map(|(_, s)| s) - .cloned() - .collect()) - } - - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) .cloned() - .ok_or(format!("{} not found", event_id.to_string())) - } - - fn auth_event_ids( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { - let mut result = vec![]; - let mut stack = event_ids.to_vec(); - - // DFS for auth event chain - while !stack.is_empty() { - let ev_id = stack.pop().unwrap(); - if result.contains(&ev_id) { - continue; - } - - result.push(ev_id.clone()); - - let event = self.get_event(room_id, &ev_id).unwrap(); - stack.extend(event.auth_events()); - } - - Ok(result) - } - - fn auth_chain_diff( - &self, - room_id: &RoomId, - event_ids: Vec>, - ) -> Result, String> { - use itertools::Itertools; - - let mut chains = vec![]; - for ids in event_ids { - // TODO state store `auth_event_ids` returns self in the event ids list - // when an event returns `auth_event_ids` self is not contained - let chain = self - .auth_event_ids(room_id, &ids)? - .into_iter() - .collect::>(); - chains.push(chain); - } - - if let Some(chain) = chains.first() { - let rest = chains.iter().skip(1).flatten().cloned().collect(); - let common = chain.intersection(&rest).collect::>(); - - Ok(chains - .iter() - .flatten() - .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() - .collect()) - } else { - Ok(vec![]) - } + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } diff --git a/src/error.rs b/src/error.rs index 4945f975..51ca8fcc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,7 +20,18 @@ pub enum Error { #[error("Not found error: {0}")] NotFound(String), - // TODO remove once the correct errors are used - #[error("an error occured {0}")] - TempString(String), + #[error("Invalid PDU: {0}")] + InvalidPdu(String), + + #[error("Conversion failed: {0}")] + ConversionError(String), + + #[error("{0}")] + Custom(Box), +} + +impl Error { + pub fn custom(e: E) -> Self { + Self::Custom(Box::new(e)) + } } diff --git a/src/event_auth.rs b/src/event_auth.rs index a904fb4d..54771408 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -83,26 +83,26 @@ pub fn auth_types_for_event( /// * then there are checks for specific event types pub fn auth_check( room_version: &RoomVersionId, - event: &StateEvent, + incoming_event: &StateEvent, prev_event: Option<&StateEvent>, auth_events: StateMap, do_sig_check: bool, ) -> Result { - tracing::info!("auth_check beginning for {}", event.event_id().as_str()); + tracing::info!("auth_check beginning for {}", incoming_event.kind()); // don't let power from other rooms be used for auth_event in auth_events.values() { - if auth_event.room_id() != event.room_id() { + if auth_event.room_id() != incoming_event.room_id() { tracing::warn!("found auth event that did not match event's room_id"); return Ok(false); } } if do_sig_check { - let sender_domain = event.sender().server_name(); + let sender_domain = incoming_event.sender().server_name(); - let is_invite_via_3pid = if event.kind() == EventType::RoomMember { - event + let is_invite_via_3pid = if incoming_event.kind() == EventType::RoomMember { + incoming_event .deserialize_content::() .map(|c| c.membership == MembershipState::Invite && c.third_party_invite.is_some()) .unwrap_or_default() @@ -111,15 +111,15 @@ pub fn auth_check( }; // check the event has been signed by the domain of the sender - if event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { + if incoming_event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { tracing::warn!("event not signed by sender's server"); return Ok(false); } - if event.room_version() == RoomVersionId::Version1 - && event + if incoming_event.room_version() == RoomVersionId::Version1 + && incoming_event .signatures() - .get(event.event_id().server_name().unwrap()) + .get(incoming_event.event_id().server_name().unwrap()) .is_none() { tracing::warn!("event not signed by event_id's server"); @@ -134,24 +134,26 @@ pub fn 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 { + if incoming_event.kind() == EventType::RoomCreate { tracing::info!("start m.room.create check"); // If it has any previous events, reject - if !event.prev_event_ids().is_empty() { + if !incoming_event.prev_event_ids().is_empty() { tracing::warn!("the room creation event had previous events"); return Ok(false); } // If the domain of the room_id does not match the domain of the sender, reject - if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { + if incoming_event.room_id().map(|id| id.server_name()) + != Some(incoming_event.sender().server_name()) + { tracing::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } // If content.room_version is present and is not a recognized version, reject if serde_json::from_value::( - event + incoming_event .content() .get("room_version") .cloned() @@ -165,7 +167,7 @@ pub fn auth_check( } // If content has no creator field, reject - if event.content().get("creator").is_none() { + if incoming_event.content().get("creator").is_none() { tracing::warn!("no creator field found in room create content"); return Ok(false); } @@ -187,10 +189,10 @@ pub fn auth_check( // [synapse] checks for federation here // 4. if type is m.room.aliases - if event.kind() == EventType::RoomAliases { + if incoming_event.kind() == EventType::RoomAliases { tracing::info!("starting m.room.aliases check"); // TODO && room_version "special case aliases auth" ?? - if event.state_key().is_none() { + if incoming_event.state_key().is_none() { tracing::warn!("no state_key field found for event"); return Ok(false); // must have state_key } @@ -202,7 +204,9 @@ pub fn auth_check( // } // If sender's domain doesn't matches state_key, reject - if event.state_key().as_deref() != Some(event.sender().server_name().as_str()) { + if incoming_event.state_key().as_deref() + != Some(incoming_event.sender().server_name().as_str()) + { tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -211,15 +215,15 @@ pub fn auth_check( return Ok(true); } - if event.kind() == EventType::RoomMember { + if incoming_event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if event.state_key().is_none() { + if incoming_event.state_key().is_none() { tracing::warn!("no state_key found for m.room.member event"); return Ok(false); } - if event + if incoming_event .deserialize_content::() .is_err() { @@ -227,7 +231,7 @@ pub fn auth_check( return Ok(false); } - if !valid_membership_change(event.to_requester(), prev_event, &auth_events)? { + if !valid_membership_change(incoming_event.to_requester(), prev_event, &auth_events)? { return Ok(false); } @@ -236,7 +240,7 @@ pub fn auth_check( } // If the sender's current membership state is not join, reject - match check_event_sender_in_room(event, &auth_events) { + match check_event_sender_in_room(incoming_event.sender(), &auth_events) { Some(true) => {} // sender in room Some(false) => { tracing::warn!("sender's membership is not join"); @@ -250,22 +254,24 @@ pub fn auth_check( // Special case to allow m.room.third_party_invite events where ever // a user is allowed to issue invites - if event.kind() == EventType::RoomThirdPartyInvite { + if incoming_event.kind() == EventType::RoomThirdPartyInvite { // TODO impl this unimplemented!("third party invite") } // If the event type's required power level is greater than the sender's power level, reject // If the event has a state_key that starts with an @ and does not match the sender, reject. - if !can_send_event(event, &auth_events)? { + if !can_send_event(incoming_event, &auth_events)? { tracing::warn!("user cannot send event"); return Ok(false); } - if event.kind() == EventType::RoomPowerLevels { + if incoming_event.kind() == EventType::RoomPowerLevels { tracing::info!("starting m.room.power_levels check"); - if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) { + if let Some(required_pwr_lvl) = + check_power_levels(room_version, incoming_event, &auth_events) + { if !required_pwr_lvl { tracing::warn!("power level was not allowed"); return Ok(false); @@ -277,8 +283,8 @@ pub fn auth_check( tracing::info!("power levels event allowed"); } - if event.kind() == EventType::RoomRedaction { - if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? { + if incoming_event.kind() == EventType::RoomRedaction { + if let RedactAllowed::No = check_redaction(room_version, incoming_event, &auth_events)? { return Ok(false); } } @@ -287,20 +293,6 @@ pub fn auth_check( Ok(true) } -/// Can this room federate based on its m.room.create event. -pub fn can_federate(auth_events: &StateMap) -> bool { - let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); - if let Some(ev) = creation_event { - if let Some(fed) = ev.content().get("m.federate") { - fed == "true" - } else { - false - } - } else { - false - } -} - /// Does the user who sent this member event have required power levels to do so. /// /// * `user` - Information about the membership event and user making the request. @@ -316,7 +308,7 @@ pub fn valid_membership_change( let state_key = if let Some(s) = user.state_key.as_ref() { s } else { - return Err(Error::TempString("State event requires state_key".into())); + return Err(Error::InvalidPdu("State event requires state_key".into())); }; let content = @@ -324,8 +316,8 @@ pub fn valid_membership_change( let target_membership = content.membership; - let target_user_id = - UserId::try_from(state_key.as_str()).map_err(|e| Error::TempString(format!("{}", e)))?; + let target_user_id = UserId::try_from(state_key.as_str()) + .map_err(|e| Error::ConversionError(format!("{}", e)))?; let key = (EventType::RoomMember, Some(user.sender.to_string())); let sender = auth_events.get(&key); @@ -464,10 +456,10 @@ pub fn valid_membership_change( /// Is the event's sender in the room that they sent the event to. pub fn check_event_sender_in_room( - event: &StateEvent, + sender: &UserId, auth_events: &StateMap, ) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, Some(event.sender().to_string())))?; + let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; // TODO this is check_membership a helper fn in synapse but it does this Some( mem.deserialize_content::() @@ -692,6 +684,20 @@ pub fn check_membership(member_event: Option<&StateEvent>, state: MembershipStat } } +/// Can this room federate based on its m.room.create event. +pub fn can_federate(auth_events: &StateMap) -> bool { + let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); + if let Some(ev) = creation_event { + if let Some(fed) = ev.content().get("m.federate") { + fed == "true" + } else { + false + } + } else { + false + } +} + /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. pub fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { diff --git a/src/lib.rs b/src/lib.rs index 42c9d197..375f22bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,7 @@ impl StateResolution { for event in event_map.values() { if event.room_id() != Some(room_id) { - return Err(Error::TempString(format!( + return Err(Error::InvalidPdu(format!( "resolving event {} in room {}, when correct room is {}", event.event_id(), event.room_id().map(|id| id.as_str()).unwrap_or("`unknown`"), @@ -288,16 +288,14 @@ impl StateResolution { tracing::debug!("calculating auth chain difference"); - store - .auth_chain_diff( - room_id, - state_sets - .iter() - .map(|map| map.values().cloned().collect()) - .dedup() - .collect::>(), - ) - .map_err(Error::TempString) + store.auth_chain_diff( + room_id, + state_sets + .iter() + .map(|map| map.values().cloned().collect()) + .dedup() + .collect::>(), + ) } /// Events are sorted from "earliest" to "latest". They are compared using diff --git a/src/state_store.rs b/src/state_store.rs index 80621bf0..777913ed 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -2,18 +2,14 @@ use std::collections::BTreeSet; use ruma::identifiers::{EventId, RoomId}; -use crate::StateEvent; +use crate::{Result, StateEvent}; pub trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { + fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { let mut events = vec![]; for id in event_ids { events.push(self.get_event(room_id, id)?); @@ -22,11 +18,7 @@ pub trait StateStore { } /// Returns a Vec of the related auth events to the given `event`. - fn auth_event_ids( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { + fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { let mut result = vec![]; let mut stack = event_ids.to_vec(); @@ -52,7 +44,7 @@ pub trait StateStore { &self, room_id: &RoomId, event_ids: Vec>, - ) -> Result, String> { + ) -> Result> { let mut chains = vec![]; for ids in event_ids { // TODO state store `auth_event_ids` returns self in the event ids list diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 6ae41778..4fb91005 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -18,7 +18,7 @@ use state_res::{ // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, valid_membership_change, }, - Requester, StateEvent, StateMap, StateStore, + Requester, StateEvent, StateMap, StateStore, Result, Error }; use tracing_subscriber as tracer; @@ -75,12 +75,12 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) .cloned() - .ok_or(format!("{} not found", event_id.to_string())) + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 4cd0317d..c9d9248a 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -12,7 +12,7 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{StateEvent, StateMap, StateStore}; +use state_res::{Error, Result, StateEvent, StateMap, StateStore}; use tracing_subscriber as tracer; use std::sync::Once; @@ -57,12 +57,12 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) .cloned() - .ok_or(format!("{} not found", event_id.to_string())) + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index b4dbab52..7b2d9d35 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -14,7 +14,9 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; +use state_res::{ + Error, ResolutionResult, Result, StateEvent, StateMap, StateResolution, StateStore, +}; use tracing_subscriber as tracer; static LOGGER: Once = Once::new(); @@ -200,12 +202,12 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) .cloned() - .ok_or(format!("{} not found", event_id.to_string())) + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } diff --git a/tests/state_res.rs b/tests/state_res.rs index 40a89e4b..aaed38ea 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,9 +1,4 @@ -use std::{ - cell::RefCell, - collections::{BTreeMap, BTreeSet}, - convert::TryFrom, - time::UNIX_EPOCH, -}; +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH}; use maplit::btreemap; use ruma::{ @@ -18,7 +13,9 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{ResolutionResult, StateEvent, StateMap, StateResolution, StateStore}; +use state_res::{ + Error, ResolutionResult, Result, StateEvent, StateMap, StateResolution, StateStore, +}; use tracing_subscriber as tracer; use std::sync::Once; @@ -768,83 +765,12 @@ pub struct TestStore(RefCell>); #[allow(unused)] impl StateStore for TestStore { - fn get_events(&self, room_id: &RoomId, events: &[EventId]) -> Result, String> { - Ok(self - .0 - .borrow() - .iter() - .filter(|e| events.contains(e.0)) - .map(|(_, s)| s) - .cloned() - .collect()) - } - - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { self.0 .borrow() .get(event_id) .cloned() - .ok_or(format!("{} not found", event_id.to_string())) - } - - fn auth_event_ids( - &self, - room_id: &RoomId, - event_ids: &[EventId], - ) -> Result, String> { - let mut result = vec![]; - let mut stack = event_ids.to_vec(); - - // DFS for auth event chain - while !stack.is_empty() { - let ev_id = stack.pop().unwrap(); - if result.contains(&ev_id) { - continue; - } - - result.push(ev_id.clone()); - - let event = self.get_event(room_id, &ev_id).unwrap(); - - stack.extend(event.auth_events()); - } - - Ok(result) - } - - fn auth_chain_diff( - &self, - room_id: &RoomId, - event_ids: Vec>, - ) -> Result, String> { - use itertools::Itertools; - - let mut chains = vec![]; - for ids in event_ids { - // TODO state store `auth_event_ids` returns self in the event ids list - // when an event returns `auth_event_ids` self is not contained - let chain = self - .auth_event_ids(room_id, &ids)? - .into_iter() - .collect::>(); - chains.push(chain); - } - - if let Some(chain) = chains.first() { - let rest = chains.iter().skip(1).flatten().cloned().collect(); - let common = chain.intersection(&rest).collect::>(); - - Ok(chains - .iter() - .flatten() - .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() - .collect()) - } else { - Ok(vec![]) - } + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } From e3de44ef2ff414e4791215fa14b0329c1b682328 Mon Sep 17 00:00:00 2001 From: q-b Date: Sat, 29 Aug 2020 12:21:14 +0200 Subject: [PATCH 046/130] Fix StateStore get_event() signature in README https://github.com/ruma/state-res/commit/5f77bc11a2d57cee3bc977dff143cea5eca4df45 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f7685f2..06b3e045 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ impl StateResolution { // The tricky part, making a good abstraction... trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, event_id: &EventId) -> Result; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; // There are 3 methods that have default implementations `get_events`, // `auth_event_ids` and `auth_chain_diff`. Each could be overridden if From 1eb89941b7e36dd881ece53fe087de5f286ec065 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 31 Aug 2020 07:21:30 -0400 Subject: [PATCH 047/130] Rename power_event -> control_event and add more docs --- src/event_auth.rs | 8 +++++ src/lib.rs | 89 ++++++++++++++++++++++++----------------------- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 6b92d52a..69c3ead7 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -90,6 +90,14 @@ pub fn auth_check( ) -> Result { tracing::info!("auth_check beginning for {}", incoming_event.kind()); + tracing::debug!( + "{:?}", + auth_events + .values() + .map(|id| id.event_id().to_string()) + .collect::>() + ); + // don't let power from other rooms be used for auth_event in auth_events.values() { if auth_event.room_id() != incoming_event.room_id() { diff --git a/src/lib.rs b/src/lib.rs index 008a5169..905e289a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,13 +51,15 @@ impl StateResolution { /// /// * `store` - Any type that implements `StateStore` acts as the database. When an event is not /// found in the `event_map` it will be retrieved from the `store`. + /// + /// It is up the the caller to check that the events returned from `StateStore::get_event` are + /// events for the correct room (synapse checks that all events are in the right room). pub fn resolve( room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], event_map: Option>, store: &dyn StateStore, - // TODO actual error handling (`thiserror`??) ) -> Result> { tracing::info!("State resolution starting"); @@ -112,33 +114,25 @@ impl StateResolution { tracing::debug!("event map size: {}", event_map.len()); - for event in event_map.values() { - if event.room_id() != Some(room_id) { - return Err(Error::InvalidPdu(format!( - "resolving event {} in room {}, when correct room is {}", - event.event_id(), - event.room_id().map(|id| id.as_str()).unwrap_or("`unknown`"), - room_id.as_str() - ))); - } - } + // we used to check that all events are events from the correct room + // this is now a check the caller of `resolve` must make. // synapse says `full_set = {eid for eid in full_conflicted_set if eid in event_map}` // // don't honor events we cannot "verify" 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 + // get only the control events with a state_key: "" or ban/kick event (sender != state_key) + let control_events = all_conflicted .iter() .filter(|id| is_power_event(id, &event_map)) .cloned() .collect::>(); - // sort the power events based on power_level/clock/event_id and outgoing/incoming edges - let mut sorted_power_levels = StateResolution::reverse_topological_power_sort( + // sort the control events based on power_level/clock/event_id and outgoing/incoming edges + let mut sorted_control_levels = StateResolution::reverse_topological_power_sort( room_id, - &power_events, + &control_events, &mut event_map, store, &all_conflicted, @@ -146,17 +140,17 @@ impl StateResolution { tracing::debug!( "SRTD {:?}", - sorted_power_levels + sorted_control_levels .iter() .map(ToString::to_string) .collect::>() ); - // sequentially auth check each power level event event. - let resolved = StateResolution::iterative_auth_check( + // sequentially auth check each control event. + let resolved_control = StateResolution::iterative_auth_check( room_id, room_version, - &sorted_power_levels, + &sorted_control_levels, &clean, &mut event_map, store, @@ -164,18 +158,18 @@ impl StateResolution { tracing::debug!( "AUTHED {:?}", - resolved + resolved_control .iter() .map(|(key, id)| (key, id.to_string())) .collect::>() ); - // At this point the power_events have been resolved we now have to + // At this point the control_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; + sorted_control_levels.dedup(); + let deduped_power_ev = sorted_control_levels; - // we have resolved the power events so remove them, I'm sure there are other reasons to do so + // This removes the control events that passed auth and more importantly those that failed auth let events_to_resolve = all_conflicted .iter() .filter(|id| !deduped_power_ev.contains(id)) @@ -190,7 +184,7 @@ impl StateResolution { .collect::>() ); - let power_event = resolved.get(&(EventType::RoomPowerLevels, Some("".into()))); + let power_event = resolved_control.get(&(EventType::RoomPowerLevels, Some("".into()))); tracing::debug!("PL {:?}", power_event); @@ -214,7 +208,7 @@ impl StateResolution { room_id, room_version, &sorted_left_events, - &resolved, + &resolved_control, // the control events are added to the final resolved state &mut event_map, store, )?; @@ -226,8 +220,11 @@ impl StateResolution { } /// Split the events that have no conflicts from those that are conflicting. + /// The return tuple looks like `(unconflicted, conflicted)`. /// - /// The tuple looks like `(unconflicted, conflicted)`. + /// State is determined to be conflicting if for the given key (EventType, StateKey) there + /// is not exactly one eventId. This includes missing events, if one state_set includes an event + /// that none of the other have this is a conflicting event. pub fn separate( state_sets: &[StateMap], ) -> (StateMap, StateMap>) { @@ -248,14 +245,6 @@ impl StateResolution { .dedup() .collect::>(); - tracing::debug!( - "SEP {:?}", - event_ids - .iter() - .map(|i| i.map(ToString::to_string).unwrap_or_else(|| "None".into())) - .collect::>() - ); - if event_ids.len() == 1 { if let Some(Some(id)) = event_ids.pop() { unconflicted_state.insert(key.clone(), id.clone()); @@ -488,13 +477,18 @@ impl StateResolution { /// Check the that each event is authenticated based on the events before it. /// - /// For each `power_events` event we gather the events needed to auth it from the + /// ## Returns + /// + /// The `unconflicted_state` combined with the newly auth'ed events. So any event that + /// fails the `event_auth::auth_check` will be excluded from the returned `StateMap`. + /// + /// For each `events_to_check` event we gather the events needed to auth it from the /// `event_map` or `store` and verify each event using the `event_auth::auth_check` /// function. pub fn iterative_auth_check( room_id: &RoomId, room_version: &RoomVersionId, - power_events: &[EventId], + events_to_check: &[EventId], unconflicted_state: &StateMap, event_map: &mut EventMap, store: &dyn StateStore, @@ -502,8 +496,8 @@ impl StateResolution { tracing::info!("starting iterative auth check"); tracing::debug!( - "{:?}", - power_events + "performing auth checks on {:?}", + events_to_check .iter() .map(ToString::to_string) .collect::>() @@ -511,7 +505,7 @@ impl StateResolution { let mut resolved_state = unconflicted_state.clone(); - for (idx, event_id) in power_events.iter().enumerate() { + for (idx, event_id) in events_to_check.iter().enumerate() { let event = StateResolution::get_or_load_event(room_id, event_id, event_map, store).unwrap(); @@ -521,7 +515,7 @@ impl StateResolution { StateResolution::get_or_load_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? - // TODO synapse check "rejected_reason", I'm guessing this is redacted_because for ruma ?? + // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); @@ -544,7 +538,7 @@ impl StateResolution { } } - tracing::debug!("event to check {:?}", event.event_id().to_string()); + tracing::debug!("event to check {:?}", event.event_id().as_str()); let most_recent_prev_event = event .prev_event_ids() @@ -579,7 +573,12 @@ impl StateResolution { } /// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using - /// the `resolved_power_level`. + /// the depth of `resolved_power_level`, the server timestamp, and the eventId. + /// + /// The depth of the given event is calculated based on the depth of it's closest "parent" + /// power_level event. If there have been two power events the after the most recent are + /// depth 0, the events before (with the first power level as a parent) will be marked + /// as depth 1. depth 1 is "older" than depth 0. pub fn mainline_sort( room_id: &RoomId, to_sort: &[EventId], @@ -664,6 +663,8 @@ impl StateResolution { } // TODO make `event` not clone every loop + /// Get the mainline depth from the `mainline_map` or finds a power_level event + /// that has an associated mainline depth. fn get_mainline_depth( room_id: &RoomId, mut event: Option, From 3cc4ae2bf7fd60248b5b26570bffee42cb734f59 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 31 Aug 2020 14:53:20 -0400 Subject: [PATCH 048/130] Remove the last few synapse-ism using only spec event auth --- src/event_auth.rs | 210 +++++++++++++++++++++++++++----------------- src/lib.rs | 12 ++- tests/event_auth.rs | 4 +- 3 files changed, 142 insertions(+), 84 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 69c3ead7..1a52cfcf 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -15,7 +15,6 @@ use ruma::{ }; use crate::{ - room_version::RoomVersion, state_event::{Requester, StateEvent}, Error, Result, StateMap, }; @@ -86,54 +85,13 @@ pub fn auth_check( incoming_event: &StateEvent, prev_event: Option<&StateEvent>, auth_events: StateMap, - do_sig_check: bool, + current_third_party_invite: Option<&StateEvent>, ) -> Result { tracing::info!("auth_check beginning for {}", incoming_event.kind()); - tracing::debug!( - "{:?}", - auth_events - .values() - .map(|id| id.event_id().to_string()) - .collect::>() - ); + // [synapse] check that all the events are in the same room as `incoming_event` - // don't let power from other rooms be used - for auth_event in auth_events.values() { - if auth_event.room_id() != incoming_event.room_id() { - tracing::warn!("found auth event that did not match event's room_id"); - return Ok(false); - } - } - - if do_sig_check { - let sender_domain = incoming_event.sender().server_name(); - - let is_invite_via_3pid = if incoming_event.kind() == EventType::RoomMember { - incoming_event - .deserialize_content::() - .map(|c| c.membership == MembershipState::Invite && c.third_party_invite.is_some()) - .unwrap_or_default() - } else { - false - }; - - // check the event has been signed by the domain of the sender - if incoming_event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid { - tracing::warn!("event not signed by sender's server"); - return Ok(false); - } - - if incoming_event.room_version() == RoomVersionId::Version1 - && incoming_event - .signatures() - .get(incoming_event.event_id().server_name().unwrap()) - .is_none() - { - tracing::warn!("event not signed by event_id's server"); - return Ok(false); - } - } + // [synapse] do_sig_check check the event has valid signatures for member events // TODO do_size_check is false when called by `iterative_auth_check` // do_size_check is also mostly accomplished by ruma with the exception of checking event_type, @@ -184,6 +142,23 @@ pub fn auth_check( return Ok(true); } + // 2. Reject if auth_events + // a. auth_events cannot have duplicate keys since it's a BTree + // b. All entries are valid auth events according to spec + let expected_auth = auth_types_for_event( + incoming_event.kind(), + incoming_event.sender(), + incoming_event.state_key(), + incoming_event.content().clone(), + ); + for ev_key in auth_events.keys() { + // (b) + if !expected_auth.contains(ev_key) { + tracing::warn!("auth_events contained invalid auth event"); + return Ok(false); + } + } + // 3. If event does not have m.room.create in auth_events reject if auth_events .get(&(EventType::RoomCreate, Some("".into()))) @@ -199,13 +174,13 @@ pub fn auth_check( // 4. if type is m.room.aliases if incoming_event.kind() == EventType::RoomAliases { tracing::info!("starting m.room.aliases check"); - // TODO && room_version "special case aliases auth" ?? + // [synapse] adds `&& room_version` "special case aliases auth" if incoming_event.state_key().is_none() { tracing::warn!("no state_key field found for event"); return Ok(false); // must have state_key } - // TODO this is not part of the spec + // [synapse] // if event.state_key().unwrap().is_empty() { // tracing::warn!("state_key must be non-empty"); // return Ok(false); // and be non-empty state_key (point to a user_id) @@ -239,7 +214,12 @@ pub fn auth_check( return Ok(false); } - if !valid_membership_change(incoming_event.to_requester(), prev_event, &auth_events)? { + if !valid_membership_change( + incoming_event.to_requester(), + current_third_party_invite, + prev_event, + &auth_events, + )? { return Ok(false); } @@ -260,11 +240,13 @@ pub fn auth_check( } } - // Special case to allow m.room.third_party_invite events where ever - // a user is allowed to issue invites - if incoming_event.kind() == EventType::RoomThirdPartyInvite { - // TODO impl this - unimplemented!("third party invite") + // Allow if and only if sender's current power level is greater than + // or equal to the invite level + if incoming_event.kind() == EventType::RoomThirdPartyInvite + && !can_send_invite(&incoming_event.to_requester(), &auth_events)? + { + tracing::warn!("sender's cannot send invites in this room"); + return Ok(false); } // If the event type's required power level is greater than the sender's power level, reject @@ -313,6 +295,7 @@ pub fn auth_check( pub fn valid_membership_change( user: Requester<'_>, prev_event: Option<&StateEvent>, + current_third_party_invite: Option<&StateEvent>, auth_events: &StateMap, ) -> Result { let state_key = if let Some(s) = user.state_key.as_ref() { @@ -422,12 +405,12 @@ pub fn valid_membership_change( || join_rules == JoinRule::Public } } else if target_membership == MembershipState::Invite { - if let Some(_tp_id) = content.third_party_invite { + // If content has third_party_invite key + if let Some(tp_id) = content.third_party_invite { if current_membership == MembershipState::Ban { false } else { - // TODO this is not filled out - verify_third_party_invite(&user, auth_events) + verify_third_party_invite(&user, &tp_id, current_third_party_invite) } } else if sender_membership != MembershipState::Join || current_membership == MembershipState::Join @@ -470,7 +453,6 @@ pub fn check_event_sender_in_room( auth_events: &StateMap, ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; - // TODO this is check_membership a helper fn in synapse but it does this Some( mem.deserialize_content::() .ok()? @@ -508,12 +490,11 @@ pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> /// Confirm that the event sender has the required power levels. pub fn check_power_levels( - room_version: &RoomVersionId, + _: &RoomVersionId, power_event: &StateEvent, auth_events: &StateMap, ) -> Option { let key = (power_event.kind(), power_event.state_key()); - let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { @@ -555,21 +536,18 @@ pub fn check_power_levels( tracing::debug!("events to check {:?}", event_levels_to_check); - // 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(); + // [synapse] validate MSC2209 depending on room version check "notifications". + // 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 { - tracing::warn!("m.room.power_level cannot add ops > than own"); - return Some(false); // cannot add ops greater than own - } - } + // 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 { + // tracing::warn!("m.room.power_level cannot add ops > than own"); + // return Some(false); // cannot add ops greater than own + // } + // } let old_state = ¤t_content; let new_state = &user_content; @@ -584,11 +562,15 @@ pub fn check_power_levels( if old_level.is_some() && new_level.is_some() && old_level == new_level { continue; } + + // If the current value is equal to the sender's current power level, reject if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { tracing::warn!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } + // If the current value is higher than the sender's current power level, reject + // If the new value is higher than the sender's current power level, reject 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 { @@ -605,6 +587,8 @@ pub fn check_power_levels( continue; } + // If the current value is higher than the sender's current power level, reject + // If the new value is higher than the sender's current power level, reject 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 { @@ -664,9 +648,17 @@ pub fn check_redaction( return Ok(RedactAllowed::CanRedact); } + // FROM SPEC: + // Redaction events are always accepted (provided the event is allowed by `events` and + // `events_default` in the power levels). However, servers should not apply or send redaction's + // to clients until both the redaction event and original event have been seen, and are valid. + // Servers should only apply redaction's to events where the sender's domains match, + // or the sender of the redaction has the appropriate permissions per the power levels. + + // version 1 check if let RoomVersionId::Version1 = room_version { - // are the redacter and redactee in the same domain - if Some(redaction_event.sender().server_name()) + // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow + if redaction_event.event_id().server_name() == redaction_event.redacts().and_then(|id| id.server_name()) { tracing::info!("redaction event allowed via room version 1 rules"); @@ -789,10 +781,66 @@ pub fn get_send_level( } } -/// TODO this is unimplemented -pub fn verify_third_party_invite( - _event: &Requester<'_>, - _auth_events: &StateMap, -) -> bool { - unimplemented!("impl third party invites") +/// Check user can send invite. +pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap) -> Result { + let user_level = get_user_power_level(event.sender, auth_events); + let key = (EventType::RoomPowerLevels, Some("".into())); + let invite_level = auth_events + .get(&key) + .map_or_else( + || Ok::<_, Error>(js_int::int!(50)), + |power_levels| { + power_levels + .deserialize_content::() + .map(|pl| pl.invite) + .map_err(Into::into) + }, + )? + .into(); + + Ok(user_level >= invite_level) +} + +pub fn verify_third_party_invite( + event: &Requester<'_>, + tp_id: &member::ThirdPartyInvite, + current_third_party_invite: Option<&StateEvent>, +) -> bool { + // 1. check for user being banned happens before this is called + // checking for mxid and token keys is done by ruma when deserializing + + if event.state_key != Some(tp_id.signed.mxid.to_string()) { + return false; + } + + // If there is no m.room.third_party_invite event in the current room state + // with state_key matching token, reject + if let Some(current_tpid) = current_third_party_invite { + if current_tpid.state_key() != Some(tp_id.signed.token.to_string()) { + return false; + } + + if event.sender != current_tpid.sender() { + return false; + } + + // If any signature in signed matches any public key in the m.room.third_party_invite event, allow + if let Ok(tpid_ev) = serde_json::from_value::< + ruma::events::room::third_party_invite::ThirdPartyInviteEventContent, + >(current_tpid.content().clone()) + { + // A list of public keys in the public_keys field + for key in tpid_ev.public_keys.unwrap_or_default() { + if key.public_key == tp_id.signed.token { + return true; + } + } + // A single public key in the public_key field + tpid_ev.public_key == tp_id.signed.token + } else { + false + } + } else { + false + } } diff --git a/src/lib.rs b/src/lib.rs index 905e289a..a6b29b06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -546,12 +546,22 @@ impl StateResolution { .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) .next_back(); + // The key for this is (eventType + a state_key of the signed token not sender) so search + // for it + let current_third_party = auth_events.iter().find_map(|(_, pdu)| { + if pdu.kind() == EventType::RoomThirdPartyInvite { + Some(pdu.clone()) // TODO no clone, auth_events is borrowed while moved + } else { + None + } + }); + if event_auth::auth_check( room_version, &event, most_recent_prev_event.as_ref(), auth_events, - false, + current_third_party.as_ref(), )? { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 4fb91005..47d04727 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -261,7 +261,7 @@ fn test_ban_pass() { sender: &alice(), }; - assert!(valid_membership_change(requester, prev, &auth_events).unwrap()) + assert!(valid_membership_change(requester, prev, None, &auth_events).unwrap()) } #[test] @@ -285,5 +285,5 @@ fn test_ban_fail() { sender: &charlie(), }; - assert!(!valid_membership_change(requester, prev, &auth_events).unwrap()) + assert!(!valid_membership_change(requester, prev, None, &auth_events).unwrap()) } From 8ca1726e9871f078b65b39f71eeffdbd4022d6f4 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 1 Sep 2020 15:14:09 -0400 Subject: [PATCH 049/130] Remove RedactAllowed enum in favor of bool --- src/event_auth.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 1a52cfcf..3317f711 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -19,16 +19,6 @@ use crate::{ Error, Result, 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, -} - /// For the given event `kind` what are the relevant auth events /// that are needed to authenticate this `content`. pub fn auth_types_for_event( @@ -274,7 +264,7 @@ pub fn auth_check( } if incoming_event.kind() == EventType::RoomRedaction { - if let RedactAllowed::No = check_redaction(room_version, incoming_event, &auth_events)? { + if !check_redaction(room_version, incoming_event, &auth_events)? { return Ok(false); } } @@ -639,13 +629,13 @@ pub fn check_redaction( room_version: &RoomVersionId, redaction_event: &StateEvent, auth_events: &StateMap, -) -> Result { +) -> Result { 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 { tracing::info!("redaction allowed via power levels"); - return Ok(RedactAllowed::CanRedact); + return Ok(true); } // FROM SPEC: @@ -662,11 +652,11 @@ pub fn check_redaction( == redaction_event.redacts().and_then(|id| id.server_name()) { tracing::info!("redaction event allowed via room version 1 rules"); - return Ok(RedactAllowed::OwnEvent); + return Ok(true); } } - Ok(RedactAllowed::No) + Ok(false) } /// Check that the member event matches `state`. From acd829336ee71725822c9606b1d6d900509d28e9 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 8 Sep 2020 16:59:29 -0400 Subject: [PATCH 050/130] If no power_level event default to 0 power_level for user --- src/event_auth.rs | 8 ++++---- src/lib.rs | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 3317f711..04acf141 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -263,10 +263,10 @@ pub fn auth_check( tracing::info!("power levels event allowed"); } - if incoming_event.kind() == EventType::RoomRedaction { - if !check_redaction(room_version, incoming_event, &auth_events)? { - return Ok(false); - } + if incoming_event.kind() == EventType::RoomRedaction + && !check_redaction(room_version, incoming_event, &auth_events)? + { + return Ok(false); } tracing::info!("allowing event passed all checks"); diff --git a/src/lib.rs b/src/lib.rs index a6b29b06..c782c41d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -438,20 +438,6 @@ impl StateResolution { } if pl.is_none() { - for aid in store.get_event(room_id, event_id).unwrap().auth_events() { - if let Ok(aev) = store.get_event(room_id, &aid) { - if aev.is_type_and_key(EventType::RoomCreate, "") { - if let Ok(content) = aev - .deserialize_content::() - { - if &content.creator == aev.sender() { - return 100; - } - break; - } - } - } - } return 0; } From f587b88a600781118c7a0e00705ad6dddc067d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Fri, 11 Sep 2020 14:36:14 +0200 Subject: [PATCH 051/130] Fixes --- Cargo.toml | 17 +++++++------ benches/state_res_bench.rs | 42 ++++++++++++++------------------ src/event_auth.rs | 50 +++++++++++++++++++------------------- src/lib.rs | 50 +++++++++++++++++++------------------- src/state_event.rs | 2 ++ src/state_store.rs | 6 ++--- tests/event_auth.rs | 25 ++++++++++--------- tests/event_sorting.rs | 18 ++++++-------- tests/res_with_auth_ids.rs | 35 +++++++++++++------------- tests/state_res.rs | 42 ++++++++++++++------------------ 10 files changed, 139 insertions(+), 148 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 779fec77..f110b0d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,15 +22,17 @@ maplit = "1.0.2" thiserror = "1.0.20" tracing-subscriber = "0.2.11" -# [dependencies.ruma] -# path = "../__forks__/ruma/ruma" -# features = ["client-api", "federation-api", "appservice-api"] - [dependencies.ruma] -git = "https://github.com/ruma/ruma" -rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" +path = "../ruma/ruma" features = ["client-api", "federation-api", "appservice-api"] +#[dependencies.ruma] +#git = "https://github.com/ruma/ruma" +#rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" +#features = ["client-api", "federation-api", "appservice-api"] + +[features] +unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] criterion = "0.3.3" @@ -38,4 +40,5 @@ rand = "0.7.3" [[bench]] name = "state_res_bench" -harness = false \ No newline at end of file +harness = false + diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index eca7d013..1a7cd76b 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -3,7 +3,7 @@ // `cargo bench unknown option --save-baseline`. // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. -use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH}; +use std::{collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH, sync::Arc}; use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; @@ -42,7 +42,7 @@ fn lexico_topo_sort(c: &mut Criterion) { fn resolution_shallow_auth_chain(c: &mut Criterion) { c.bench_function("resolve state of 5 events one fork", |b| { - let store = TestStore(RefCell::new(btreemap! {})); + let mut store = TestStore(btreemap! {}); // build up the DAG let (state_at_bob, state_at_charlie, _) = store.set_up(); @@ -69,7 +69,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { let mut inner = init; inner.extend(ban); - let store = TestStore(RefCell::new(inner.clone())); + let store = TestStore(inner.clone()); let state_set_a = [ inner.get(&event_id("CREATE")).unwrap(), @@ -126,22 +126,21 @@ criterion_main!(benches); // IMPLEMENTATION DETAILS AHEAD // /////////////////////////////////////////////////////////////////////*/ -pub struct TestStore(RefCell>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 - .borrow() .get(event_id) - .cloned() + .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } impl TestStore { - pub fn set_up(&self) -> (StateMap, StateMap, StateMap) { - let create_event = to_pdu_event::( + pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { + let create_event = Arc::new(to_pdu_event::( "CREATE", alice(), EventType::RoomCreate, @@ -149,11 +148,10 @@ impl TestStore { json!({ "creator": alice() }), &[], &[], - ); + )); let cre = create_event.event_id(); self.0 - .borrow_mut() - .insert(cre.clone(), create_event.clone()); + .insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( "IMA", @@ -165,8 +163,7 @@ impl TestStore { &[cre.clone()], ); self.0 - .borrow_mut() - .insert(alice_mem.event_id(), alice_mem.clone()); + .insert(alice_mem.event_id(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -178,8 +175,7 @@ impl TestStore { &[alice_mem.event_id()], ); self.0 - .borrow_mut() - .insert(join_rules.event_id(), join_rules.clone()); + .insert(join_rules.event_id(), Arc::clone(&join_rules)); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -193,8 +189,7 @@ impl TestStore { &[join_rules.event_id()], ); self.0 - .borrow_mut() - .insert(bob_mem.event_id(), bob_mem.clone()); + .insert(bob_mem.event_id(), Arc::clone(&bob_mem)); let charlie_mem = to_pdu_event( "IMC", @@ -206,8 +201,7 @@ impl TestStore { &[join_rules.event_id()], ); self.0 - .borrow_mut() - .insert(charlie_mem.event_id(), charlie_mem.clone()); + .insert(charlie_mem.event_id(), Arc::clone(&charlie_mem)); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() @@ -288,7 +282,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> StateEvent +) -> Arc where S: AsRef, { @@ -362,12 +356,12 @@ where "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap { +fn INITIAL_EVENTS() -> BTreeMap> { vec![ to_pdu_event::( "CREATE", @@ -449,7 +443,7 @@ fn INITIAL_EVENTS() -> BTreeMap { // all graphs start with these input events #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", diff --git a/src/event_auth.rs b/src/event_auth.rs index 04acf141..462ec042 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, convert::TryFrom}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use maplit::btreeset; use ruma::{ @@ -72,10 +72,10 @@ pub fn auth_types_for_event( /// * then there are checks for specific event types pub fn auth_check( room_version: &RoomVersionId, - incoming_event: &StateEvent, - prev_event: Option<&StateEvent>, - auth_events: StateMap, - current_third_party_invite: Option<&StateEvent>, + incoming_event: &Arc, + prev_event: Option>, + auth_events: StateMap>, + current_third_party_invite: Option>, ) -> Result { tracing::info!("auth_check beginning for {}", incoming_event.kind()); @@ -206,8 +206,8 @@ pub fn auth_check( if !valid_membership_change( incoming_event.to_requester(), - current_third_party_invite, prev_event, + current_third_party_invite, &auth_events, )? { return Ok(false); @@ -241,7 +241,7 @@ pub fn auth_check( // If the event type's required power level is greater than the sender's power level, reject // If the event has a state_key that starts with an @ and does not match the sender, reject. - if !can_send_event(incoming_event, &auth_events)? { + if !can_send_event(&incoming_event, &auth_events)? { tracing::warn!("user cannot send event"); return Ok(false); } @@ -250,7 +250,7 @@ pub fn auth_check( tracing::info!("starting m.room.power_levels check"); if let Some(required_pwr_lvl) = - check_power_levels(room_version, incoming_event, &auth_events) + check_power_levels(room_version, &incoming_event, &auth_events) { if !required_pwr_lvl { tracing::warn!("power level was not allowed"); @@ -284,9 +284,9 @@ pub fn auth_check( /// the current State. pub fn valid_membership_change( user: Requester<'_>, - prev_event: Option<&StateEvent>, - current_third_party_invite: Option<&StateEvent>, - auth_events: &StateMap, + prev_event: Option>, + current_third_party_invite: Option>, + auth_events: &StateMap>, ) -> Result { let state_key = if let Some(s) = user.state_key.as_ref() { s @@ -377,7 +377,7 @@ pub fn valid_membership_change( .join_rule; } - if let Some(prev) = prev_event { + if let Some(prev) = dbg!(prev_event) { if prev.kind() == EventType::RoomCreate && prev.prev_event_ids().is_empty() { return Ok(true); } @@ -440,7 +440,7 @@ pub fn valid_membership_change( /// Is the event's sender in the room that they sent the event to. pub fn check_event_sender_in_room( sender: &UserId, - auth_events: &StateMap, + auth_events: &StateMap>, ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; Some( @@ -453,7 +453,7 @@ pub fn check_event_sender_in_room( /// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// have the correct userId as it's state_key if it's not the "" state_key. -pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> Result { +pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> Result { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); let event_type_power_level = get_send_level(event.kind(), event.state_key(), ple); @@ -481,8 +481,8 @@ pub fn can_send_event(event: &StateEvent, auth_events: &StateMap) -> /// Confirm that the event sender has the required power levels. pub fn check_power_levels( _: &RoomVersionId, - power_event: &StateEvent, - auth_events: &StateMap, + power_event: &Arc, + auth_events: &StateMap>, ) -> Option { let key = (power_event.kind(), power_event.state_key()); let current_state = if let Some(current_state) = auth_events.get(&key) { @@ -627,8 +627,8 @@ fn get_deserialize_levels( /// Does the event redacting come from a user with enough power to redact the given event. pub fn check_redaction( room_version: &RoomVersionId, - redaction_event: &StateEvent, - auth_events: &StateMap, + redaction_event: &Arc, + auth_events: &StateMap>, ) -> Result { let user_level = get_user_power_level(redaction_event.sender(), auth_events); let redact_level = get_named_level(auth_events, "redact", 50); @@ -662,7 +662,7 @@ pub fn check_redaction( /// Check that the member event matches `state`. /// /// This function returns false instead of failing when deserialization fails. -pub fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) -> bool { +pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { if let Some(event) = member_event { if let Ok(content) = serde_json::from_value::(event.content().clone()) @@ -677,7 +677,7 @@ pub fn check_membership(member_event: Option<&StateEvent>, state: MembershipStat } /// Can this room federate based on its m.room.create event. -pub fn can_federate(auth_events: &StateMap) -> bool { +pub fn can_federate(auth_events: &StateMap>) -> bool { let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); if let Some(ev) = creation_event { if let Some(fed) = ev.content().get("m.federate") { @@ -692,7 +692,7 @@ pub fn can_federate(auth_events: &StateMap) -> bool { /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. -pub fn get_named_level(auth_events: &StateMap, name: &str, default: i64) -> i64 { +pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { // TODO do this the right way and deserialize @@ -708,7 +708,7 @@ pub fn get_named_level(auth_events: &StateMap, name: &str, default: /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// object. -pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap) -> i64 { +pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { if let Ok(content) = pl.deserialize_content::() { @@ -744,7 +744,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap pub fn get_send_level( e_type: EventType, state_key: Option, - power_lvl: Option<&StateEvent>, + power_lvl: Option<&Arc>, ) -> i64 { tracing::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { @@ -772,7 +772,7 @@ pub fn get_send_level( } /// Check user can send invite. -pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap) -> Result { +pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap>) -> Result { let user_level = get_user_power_level(event.sender, auth_events); let key = (EventType::RoomPowerLevels, Some("".into())); let invite_level = auth_events @@ -794,7 +794,7 @@ pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap pub fn verify_third_party_invite( event: &Requester<'_>, tp_id: &member::ThirdPartyInvite, - current_third_party_invite: Option<&StateEvent>, + current_third_party_invite: Option>, ) -> bool { // 1. check for user being banned happens before this is called // checking for mxid and token keys is done by ruma when deserializing diff --git a/src/lib.rs b/src/lib.rs index c782c41d..8f282139 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use std::{ cmp::Reverse, collections::{BTreeMap, BTreeSet, BinaryHeap}, time::SystemTime, -}; +sync::Arc}; use maplit::btreeset; use ruma::{ @@ -58,7 +58,7 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], - event_map: Option>, + event_map: Option>>, store: &dyn StateStore, ) -> Result> { tracing::info!("State resolution starting"); @@ -292,7 +292,7 @@ impl StateResolution { pub fn reverse_topological_power_sort( room_id: &RoomId, events_to_sort: &[EventId], - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, auth_diff: &[EventId], ) -> Vec { @@ -418,7 +418,7 @@ impl StateResolution { fn get_power_level_for_sender( room_id: &RoomId, event_id: &EventId, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event ({}) senders power level", event_id.to_string()); @@ -476,7 +476,7 @@ impl StateResolution { room_version: &RoomVersionId, events_to_check: &[EventId], unconflicted_state: &StateMap, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -526,8 +526,8 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().as_str()); - let most_recent_prev_event = event - .prev_event_ids() + let most_recent_prev_event = dbg!(event + .prev_event_ids()) .iter() .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) .next_back(); @@ -545,9 +545,9 @@ impl StateResolution { if event_auth::auth_check( room_version, &event, - most_recent_prev_event.as_ref(), + most_recent_prev_event, auth_events, - current_third_party.as_ref(), + current_third_party, )? { // add event to resolved state map resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); @@ -579,7 +579,7 @@ impl StateResolution { room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of events"); @@ -658,14 +658,13 @@ impl StateResolution { sort_event_ids } - // TODO make `event` not clone every loop /// Get the mainline depth from the `mainline_map` or finds a power_level event /// that has an associated mainline depth. fn get_mainline_depth( room_id: &RoomId, - mut event: Option, + mut event: Option>, mainline_map: &EventMap, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> usize { while let Some(sort_ev) = event { @@ -681,7 +680,7 @@ impl StateResolution { let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); if aev.is_type_and_key(EventType::RoomPowerLevels, "") { - event = Some(aev.clone()); + event = Some(aev); break; } } @@ -694,7 +693,7 @@ impl StateResolution { room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, auth_diff: &[EventId], ) { @@ -730,21 +729,22 @@ impl StateResolution { fn get_or_load_event( room_id: &RoomId, ev_id: &EventId, - event_map: &mut EventMap, + event_map: &mut EventMap>, store: &dyn StateStore, - ) -> Option { - // TODO can we cut down on the clones? - if !event_map.contains_key(ev_id) { - let event = store.get_event(room_id, ev_id).ok()?; - event_map.insert(ev_id.clone(), event.clone()); - Some(event) - } else { - event_map.get(ev_id).cloned() + ) -> Option> { + if let Some(e) = event_map.get(ev_id) { + return Some(Arc::clone(e)); } + + if let Ok(e) = store.get_event(room_id, ev_id) { + return Some(Arc::clone(event_map.entry(ev_id.clone()).or_insert(e))) + } + + None } } -pub fn is_power_event(event_id: &EventId, event_map: &EventMap) -> bool { +pub fn is_power_event(event_id: &EventId, event_map: &EventMap>) -> bool { match event_map.get(event_id) { Some(state) => state.is_power_event(), _ => false, diff --git a/src/state_event.rs b/src/state_event.rs index e15d0531..96b2aae7 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -202,6 +202,8 @@ impl StateEvent { }, } } + + #[cfg(not(feature = "unstable-pre-spec"))] pub fn origin(&self) -> String { match self { Self::Full(ev) => match ev { diff --git a/src/state_store.rs b/src/state_store.rs index 777913ed..c1695fa4 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, sync::Arc}; use ruma::identifiers::{EventId, RoomId}; @@ -6,10 +6,10 @@ use crate::{Result, StateEvent}; pub trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { + fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { let mut events = vec![]; for id in event_ids { events.push(self.get_event(room_id, id)?); diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 47d04727..7ed7d44a 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use ruma::{ events::{ @@ -71,15 +71,14 @@ fn member_content_join() -> JsonValue { .unwrap() } -pub struct TestStore(RefCell>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 - .borrow() .get(event_id) - .cloned() + .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } @@ -92,7 +91,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> StateEvent +) -> Arc where S: AsRef, { @@ -166,12 +165,12 @@ where "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap { +fn INITIAL_EVENTS() -> BTreeMap> { // this is always called so we can init the logger here let _ = LOGGER.call_once(|| { tracer::fmt() @@ -246,11 +245,12 @@ fn test_ban_pass() { let prev = events .values() - .find(|ev| ev.event_id().as_str().contains("IMC")); + .find(|ev| ev.event_id().as_str().contains("IMC")) + .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) .collect::>(); let requester = Requester { @@ -270,11 +270,12 @@ fn test_ban_fail() { let prev = events .values() - .find(|ev| ev.event_id().as_str().contains("IMC")); + .find(|ev| ev.event_id().as_str().contains("IMC")) + .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) .collect::>(); let requester = Requester { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index c9d9248a..e4ac5b44 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use ruma::{ events::{ @@ -53,15 +53,14 @@ fn member_content_join() -> JsonValue { .unwrap() } -pub struct TestStore(RefCell>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 - .borrow() .get(event_id) - .cloned() + .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } @@ -74,7 +73,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> StateEvent +) -> Arc where S: AsRef, { @@ -148,12 +147,12 @@ where "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap { +fn INITIAL_EVENTS() -> BTreeMap> { // this is always called so we can init the logger here let _ = LOGGER.call_once(|| { tracer::fmt() @@ -243,8 +242,7 @@ fn shuffle(list: &mut [EventId]) { fn test_event_sort() { let mut events = INITIAL_EVENTS(); - - let store = TestStore(RefCell::new(events.clone())); + let store = TestStore(events.clone()); let event_map = events .values() diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index c00d5ffa..0787eb7a 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -1,6 +1,6 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call)] -use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, sync::Once, time::UNIX_EPOCH}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Once, time::UNIX_EPOCH, sync::Arc}; use ruma::{ events::{ @@ -21,7 +21,7 @@ static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; -fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { +fn do_check(events: &[Arc], edges: Vec>, expected_state_ids: Vec) { // to activate logging use `RUST_LOG=debug cargo t` let _ = LOGGER.call_once(|| { tracer::fmt() @@ -29,13 +29,13 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let store = TestStore(RefCell::new( + let mut store = TestStore( INITIAL_EVENTS() .values() .chain(events) .map(|ev| (ev.event_id(), ev.clone())) .collect(), - )); + ); // This will be lexi_topo_sorted for resolution let mut graph = BTreeMap::new(); @@ -64,7 +64,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } // event_id -> StateEvent - let mut event_map: BTreeMap = BTreeMap::new(); + let mut event_map: BTreeMap> = BTreeMap::new(); // event_id -> StateMap let mut state_at_event: BTreeMap> = BTreeMap::new(); @@ -152,10 +152,10 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // we have to update our store, an actual user of this lib would // be giving us state from a DB. - *store.0.borrow_mut().get_mut(&ev_id).unwrap() = event.clone(); + store.0.insert(ev_id.clone(), event.clone()); state_at_event.insert(node, state_after); - event_map.insert(event_id.clone(), event); + event_map.insert(event_id.clone(), Arc::clone(store.0.get(&ev_id).unwrap())); } let mut expected_state = StateMap::new(); @@ -186,15 +186,14 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: assert_eq!(expected_state, end_state); } -pub struct TestStore(RefCell>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 - .borrow() .get(event_id) - .cloned() + .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } @@ -256,7 +255,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> StateEvent +) -> Arc where S: AsRef, { @@ -330,12 +329,12 @@ where "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap { +fn INITIAL_EVENTS() -> BTreeMap> { // this is always called so we can init the logger here let _ = LOGGER.call_once(|| { tracer::fmt() @@ -432,7 +431,7 @@ fn INITIAL_EDGES() -> Vec { // all graphs start with these input events #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -499,7 +498,7 @@ fn ban_with_auth_chains() { #[test] fn base_with_auth_chains() { - let store = TestStore(RefCell::new(INITIAL_EVENTS())); + let store = TestStore(INITIAL_EVENTS()); let resolved: BTreeMap<_, EventId> = match StateResolution::resolve(&room_id(), &RoomVersionId::Version2, &[], None, &store) { @@ -537,7 +536,7 @@ fn ban_with_auth_chains2() { let mut inner = init.clone(); inner.extend(ban); - let store = TestStore(RefCell::new(inner.clone())); + let store = TestStore(inner.clone()); let state_set_a = [ inner.get(&event_id("CREATE")).unwrap(), @@ -607,7 +606,7 @@ fn ban_with_auth_chains2() { // all graphs start with these input events #[allow(non_snake_case)] -fn JOIN_RULE() -> BTreeMap { +fn JOIN_RULE() -> BTreeMap> { vec![ to_pdu_event( "JR", diff --git a/tests/state_res.rs b/tests/state_res.rs index 7dbe30f8..6d72617e 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH}; +use std::{collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH, sync::Arc}; use maplit::btreemap; use ruma::{ @@ -78,7 +78,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> StateEvent +) -> Arc where S: AsRef, { @@ -152,7 +152,7 @@ where "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } fn to_init_pdu_event( @@ -161,7 +161,7 @@ fn to_init_pdu_event( ev_type: EventType, state_key: Option<&str>, content: JsonValue, -) -> StateEvent { +) -> Arc { let ts = unsafe { let ts = SERVER_TIMESTAMP; // increment the "origin_server_ts" value @@ -206,12 +206,12 @@ fn to_init_pdu_event( "signatures": {}, }) }; - serde_json::from_value(json).unwrap() + Arc::new(serde_json::from_value(json).unwrap()) } // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap { +fn INITIAL_EVENTS() -> BTreeMap> { vec![ to_init_pdu_event( "CREATE", @@ -280,7 +280,7 @@ fn INITIAL_EDGES() -> Vec { .collect::>() } -fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: Vec) { +fn do_check(events: &[Arc], edges: Vec>, expected_state_ids: Vec) { // to activate logging use `RUST_LOG=debug cargo t one_test_only` let _ = LOGGER.call_once(|| { tracer::fmt() @@ -288,13 +288,13 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: .init() }); - let store = TestStore(RefCell::new( + let mut store = TestStore( INITIAL_EVENTS() .values() .chain(events) .map(|ev| (ev.event_id(), ev.clone())) .collect(), - )); + ); // This will be lexi_topo_sorted for resolution let mut graph = BTreeMap::new(); @@ -329,7 +329,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: } // event_id -> StateEvent - let mut event_map: BTreeMap = BTreeMap::new(); + let mut event_map: BTreeMap> = BTreeMap::new(); // event_id -> StateMap let mut state_at_event: BTreeMap> = BTreeMap::new(); @@ -420,7 +420,7 @@ fn do_check(events: &[StateEvent], edges: Vec>, expected_state_ids: // TODO // TODO we need to convert the `StateResolution::resolve` to use the event_map // because the user of this crate cannot update their DB's state. - *store.0.borrow_mut().get_mut(&ev_id).unwrap() = event.clone(); + store.0.insert(ev_id.clone(), Arc::clone(&event)); state_at_event.insert(node, state_after); event_map.insert(event_id.clone(), event); @@ -702,7 +702,7 @@ fn topic_setting() { #[test] fn test_event_map_none() { - let store = TestStore(RefCell::new(btreemap! {})); + let mut store = TestStore(btreemap! {}); // build up the DAG let (state_at_bob, state_at_charlie, expected) = store.set_up(); @@ -748,21 +748,20 @@ fn test_lexicographical_sort() { // /// The test state store. -pub struct TestStore(RefCell>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 - .borrow() .get(event_id) - .cloned() + .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } } impl TestStore { - pub fn set_up(&self) -> (StateMap, StateMap, StateMap) { + pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { // to activate logging use `RUST_LOG=debug cargo t one_test_only` let _ = LOGGER.call_once(|| { tracer::fmt() @@ -780,8 +779,7 @@ impl TestStore { ); let cre = create_event.event_id(); self.0 - .borrow_mut() - .insert(cre.clone(), create_event.clone()); + .insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( "IMA", @@ -793,8 +791,7 @@ impl TestStore { &[cre.clone()], ); self.0 - .borrow_mut() - .insert(alice_mem.event_id(), alice_mem.clone()); + .insert(alice_mem.event_id(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -806,7 +803,6 @@ impl TestStore { &[alice_mem.event_id()], ); self.0 - .borrow_mut() .insert(join_rules.event_id(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork @@ -821,7 +817,6 @@ impl TestStore { &[join_rules.event_id()], ); self.0 - .borrow_mut() .insert(bob_mem.event_id(), bob_mem.clone()); let charlie_mem = to_pdu_event( @@ -834,7 +829,6 @@ impl TestStore { &[join_rules.event_id()], ); self.0 - .borrow_mut() .insert(charlie_mem.event_id(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] From ad4fb6420abf30d474cdea1eec3784c3ab2a8340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Sat, 12 Sep 2020 21:17:37 +0200 Subject: [PATCH 052/130] Various improvements --- Cargo.toml | 14 +++++------ src/event_auth.rs | 2 +- src/lib.rs | 8 +++--- src/state_event.rs | 63 +++++++--------------------------------------- 4 files changed, 21 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f110b0d2..23d9af1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,15 +22,16 @@ maplit = "1.0.2" thiserror = "1.0.20" tracing-subscriber = "0.2.11" -[dependencies.ruma] -path = "../ruma/ruma" -features = ["client-api", "federation-api", "appservice-api"] - #[dependencies.ruma] -#git = "https://github.com/ruma/ruma" -#rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" +#path = "../ruma/ruma" #features = ["client-api", "federation-api", "appservice-api"] +[dependencies.ruma] +git = "https://github.com/timokoesters/ruma" +branch = "timo-fed-fixes" +#rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" +features = ["client-api", "federation-api", "appservice-api"] + [features] unstable-pre-spec = ["ruma/unstable-pre-spec"] @@ -41,4 +42,3 @@ rand = "0.7.3" [[bench]] name = "state_res_bench" harness = false - diff --git a/src/event_auth.rs b/src/event_auth.rs index 462ec042..69cdf1bf 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -377,7 +377,7 @@ pub fn valid_membership_change( .join_rule; } - if let Some(prev) = dbg!(prev_event) { + if let Some(prev) = prev_event { if prev.kind() == EventType::RoomCreate && prev.prev_event_ids().is_empty() { return Ok(true); } diff --git a/src/lib.rs b/src/lib.rs index 8f282139..6dec8588 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); + event_map.extend(events.into_iter().map(|ev| (ev.event_id().clone(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -337,7 +337,7 @@ impl StateResolution { // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, *ev.origin_server_ts(), ev.event_id()) + (-*pl, *ev.origin_server_ts(), ev.event_id().clone()) }) } @@ -526,8 +526,8 @@ impl StateResolution { tracing::debug!("event to check {:?}", event.event_id().as_str()); - let most_recent_prev_event = dbg!(event - .prev_event_ids()) + let most_recent_prev_event = event + .prev_event_ids() .iter() .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) .next_back(); diff --git a/src/state_event.rs b/src/state_event.rs index 96b2aae7..71032b29 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,17 +1,15 @@ -use std::{collections::BTreeMap, convert::TryFrom}; +use std::collections::BTreeMap; use js_int::UInt; use ruma::{ events::{ - from_raw_json_value, pdu::{EventHash, Pdu, PduStub}, room::member::{MemberEventContent, MembershipState}, - EventDeHelper, EventType, + EventType, }, EventId, RoomId, RoomVersionId, ServerName, UserId, }; -use serde::{de, Serialize}; -use serde_json::value::RawValue as RawJsonValue; +use serde::{Serialize, Deserialize}; use std::time::SystemTime; pub struct Requester<'a> { @@ -22,10 +20,11 @@ pub struct Requester<'a> { pub sender: &'a UserId, } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum StateEvent { Full(Pdu), + #[serde(skip_deserializing)] Sync(PduStub), } @@ -119,27 +118,13 @@ impl StateEvent { }, } } - pub fn event_id(&self) -> EventId { + pub fn event_id(&self) -> &EventId { match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), - Pdu::RoomV3Pdu(_) => EventId::try_from(&*format!( - "${}", - ruma::signatures::reference_hash( - &serde_json::to_value(&ev).expect("event is valid, we just created it") - ) - .expect("ruma can calculate reference hashes") - )) - .expect("ruma's reference hashes are valid event ids"), + Pdu::RoomV1Pdu(ev) => &ev.event_id, + Pdu::RoomV3Pdu(ev) => ev.event_id.as_ref().expect("RoomV3Pdu did not have an event id"), }, - Self::Sync(ev) => EventId::try_from(&*format!( - "${}", - ruma::signatures::reference_hash( - &serde_json::to_value(&ev).expect("event is valid, we just created it") - ) - .expect("ruma can calculate reference hashes") - )) - .expect("ruma's reference hashes are valid event ids"), + Self::Sync(_ev) => panic!("Stubs don't have an event id"), } } @@ -352,33 +337,3 @@ impl StateEvent { } } } - -impl<'de> de::Deserialize<'de> for StateEvent { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let json = Box::::deserialize(deserializer)?; - let EventDeHelper { - room_id, unsigned, .. - } = from_raw_json_value(&json)?; - - // Determine whether the event is a full or sync - // based on the fields present. - if room_id.is_some() { - Ok(match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Full(Pdu::RoomV1Pdu(from_raw_json_value(&json)?)), - }) - } else { - Ok(match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Sync(from_raw_json_value(&json)?), - }) - } - } -} From 33232f0a63f4e293fb2b931be659f20e0b57f104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Sun, 13 Sep 2020 22:19:48 +0200 Subject: [PATCH 053/130] Disable buggy auth_events check --- src/event_auth.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event_auth.rs b/src/event_auth.rs index 69cdf1bf..e72c3c2d 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -132,6 +132,7 @@ pub fn auth_check( return Ok(true); } + /* // 2. Reject if auth_events // a. auth_events cannot have duplicate keys since it's a BTree // b. All entries are valid auth events according to spec @@ -141,6 +142,9 @@ pub fn auth_check( incoming_event.state_key(), incoming_event.content().clone(), ); + + dbg!(&expected_auth); + for ev_key in auth_events.keys() { // (b) if !expected_auth.contains(ev_key) { @@ -148,6 +152,7 @@ pub fn auth_check( return Ok(false); } } + */ // 3. If event does not have m.room.create in auth_events reject if auth_events From 776c476f3ceb8116ba81039cff25a26da2be86af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 15 Sep 2020 12:15:11 +0200 Subject: [PATCH 054/130] Skip events that fail auth --- src/lib.rs | 61 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6dec8588..6004c3a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ use std::{ cmp::Reverse, collections::{BTreeMap, BTreeSet, BinaryHeap}, + sync::Arc, time::SystemTime, -sync::Arc}; +}; use maplit::btreeset; use ruma::{ @@ -620,29 +621,32 @@ impl StateResolution { .enumerate() .map(|(idx, eid)| ((*eid).clone(), idx)) .collect::>(); - let mut sort_event_ids = to_sort.to_vec(); let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - let event = StateResolution::get_or_load_event(room_id, ev_id, event_map, store); - let depth = StateResolution::get_mainline_depth( - room_id, - event, - &mainline_map, - event_map, - store, - ); - order_map.insert( - ev_id, - ( - depth, - event_map - .get(ev_id) - .map(|ev| ev.origin_server_ts()) - .cloned(), - ev_id, // TODO should this be a &str to sort lexically?? - ), - ); + if let Some(event) = + StateResolution::get_or_load_event(room_id, ev_id, event_map, store) + { + if let Ok(depth) = StateResolution::get_mainline_depth( + room_id, + Some(event), + &mainline_map, + event_map, + store, + ) { + order_map.insert( + ev_id, + ( + depth, + event_map + .get(ev_id) + .map(|ev| ev.origin_server_ts()) + .cloned(), + ev_id, // TODO should this be a &str to sort lexically?? + ), + ); + } + } // We yield occasionally when we're working with large data sets to // ensure that we don't block the reactor loop for too long. @@ -653,6 +657,7 @@ impl StateResolution { // sort the event_ids by their depth, timestamp and EventId // unwrap is OK order map and sort_event_ids are from to_sort (the same Vec) + let mut sort_event_ids = order_map.keys().map(|&k| k.clone()).collect::>(); sort_event_ids.sort_by_key(|sort_id| order_map.get(sort_id).unwrap()); sort_event_ids @@ -666,19 +671,21 @@ impl StateResolution { mainline_map: &EventMap, event_map: &mut EventMap>, store: &dyn StateStore, - ) -> usize { + ) -> Result { while let Some(sort_ev) = event { tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); let id = sort_ev.event_id(); if let Some(depth) = mainline_map.get(&id) { - return *depth; + return Ok(*depth); } + dbg!(&sort_ev); let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { - let aev = - StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); + dbg!(&aid); + let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store) + .ok_or(Error::NotFound("Auth event not found".to_owned()))?; if aev.is_type_and_key(EventType::RoomPowerLevels, "") { event = Some(aev); break; @@ -686,7 +693,7 @@ impl StateResolution { } } // Did not find a power level event so we default to zero - 0 + Ok(0) } fn add_event_and_auth_chain_to_graph( @@ -737,7 +744,7 @@ impl StateResolution { } if let Ok(e) = store.get_event(room_id, ev_id) { - return Some(Arc::clone(event_map.entry(ev_id.clone()).or_insert(e))) + return Some(Arc::clone(event_map.entry(ev_id.clone()).or_insert(e))); } None From 369703a6fa3803e48b60aed1af1573ab7967b33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Fri, 18 Sep 2020 10:26:29 +0200 Subject: [PATCH 055/130] Expect all state events to have a state key --- src/event_auth.rs | 76 +++++++++++++++++++++------------------------- src/lib.rs | 6 ++-- src/state_event.rs | 6 ++-- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index e72c3c2d..7a62b6a2 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -26,35 +26,37 @@ pub fn auth_types_for_event( sender: &UserId, state_key: Option, content: serde_json::Value, -) -> Vec<(EventType, Option)> { +) -> Vec<(EventType, String)> { if kind == EventType::RoomCreate { return vec![]; } let mut auth_types = vec![ - (EventType::RoomPowerLevels, Some("".to_string())), - (EventType::RoomMember, Some(sender.to_string())), - (EventType::RoomCreate, Some("".to_string())), + (EventType::RoomPowerLevels, "".to_string()), + (EventType::RoomMember, sender.to_string()), + (EventType::RoomCreate, "".to_string()), ]; if kind == EventType::RoomMember { if let Ok(content) = serde_json::from_value::(content) { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { - let key = (EventType::RoomJoinRules, Some("".into())); + let key = (EventType::RoomJoinRules, "".into()); if !auth_types.contains(&key) { auth_types.push(key) } } // TODO what when we don't find a state_key - let key = (EventType::RoomMember, state_key); - if !auth_types.contains(&key) { - auth_types.push(key) + if let Some(state_key) = state_key { + let key = (EventType::RoomMember, state_key); + if !auth_types.contains(&key) { + auth_types.push(key) + } } if content.membership == MembershipState::Invite { if let Some(t_id) = content.third_party_invite { - let key = (EventType::RoomThirdPartyInvite, Some(t_id.signed.token)); + let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); if !auth_types.contains(&key) { auth_types.push(key) } @@ -156,7 +158,7 @@ pub fn auth_check( // 3. If event does not have m.room.create in auth_events reject if auth_events - .get(&(EventType::RoomCreate, Some("".into()))) + .get(&(EventType::RoomCreate, "".into())) .is_none() { tracing::warn!("no m.room.create event in auth chain"); @@ -170,10 +172,6 @@ pub fn auth_check( if incoming_event.kind() == EventType::RoomAliases { tracing::info!("starting m.room.aliases check"); // [synapse] adds `&& room_version` "special case aliases auth" - if incoming_event.state_key().is_none() { - tracing::warn!("no state_key field found for event"); - return Ok(false); // must have state_key - } // [synapse] // if event.state_key().unwrap().is_empty() { @@ -182,8 +180,8 @@ pub fn auth_check( // } // If sender's domain doesn't matches state_key, reject - if incoming_event.state_key().as_deref() - != Some(incoming_event.sender().server_name().as_str()) + if incoming_event.state_key() + != incoming_event.sender().server_name().as_str() { tracing::warn!("state_key does not match sender"); return Ok(false); @@ -196,11 +194,6 @@ pub fn auth_check( if incoming_event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if incoming_event.state_key().is_none() { - tracing::warn!("no state_key found for m.room.member event"); - return Ok(false); - } - if incoming_event .deserialize_content::() .is_err() @@ -246,7 +239,7 @@ pub fn auth_check( // If the event type's required power level is greater than the sender's power level, reject // If the event has a state_key that starts with an @ and does not match the sender, reject. - if !can_send_event(&incoming_event, &auth_events)? { + if !can_send_event(&incoming_event, &auth_events) { tracing::warn!("user cannot send event"); return Ok(false); } @@ -307,7 +300,7 @@ pub fn valid_membership_change( let target_user_id = UserId::try_from(state_key.as_str()) .map_err(|e| Error::ConversionError(format!("{}", e)))?; - let key = (EventType::RoomMember, Some(user.sender.to_string())); + let key = (EventType::RoomMember, user.sender.to_string()); let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { @@ -316,7 +309,7 @@ pub fn valid_membership_change( .membership) })?; - let key = (EventType::RoomMember, Some(target_user_id.to_string())); + let key = (EventType::RoomMember, target_user_id.to_string()); let current = auth_events.get(&key); let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { @@ -325,7 +318,7 @@ pub fn valid_membership_change( .membership) })?; - let key = (EventType::RoomPowerLevels, Some("".into())); + let key = (EventType::RoomPowerLevels, "".into()); let power_levels = auth_events.get(&key).map_or_else( || { Ok::<_, Error>(power_levels::PowerLevelsEventContent { @@ -373,7 +366,7 @@ pub fn valid_membership_change( Some, ); - let key = (EventType::RoomJoinRules, Some("".to_string())); + let key = (EventType::RoomJoinRules, "".to_string()); let join_rules_event = auth_events.get(&key); let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { @@ -447,7 +440,7 @@ pub fn check_event_sender_in_room( sender: &UserId, auth_events: &StateMap>, ) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; + let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; Some( mem.deserialize_content::() .ok()? @@ -458,10 +451,10 @@ pub fn check_event_sender_in_room( /// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// have the correct userId as it's state_key if it's not the "" state_key. -pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> Result { - let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); +pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { + let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); - let event_type_power_level = get_send_level(event.kind(), event.state_key(), ple); + let event_type_power_level = get_send_level(event.kind(), Some(event.state_key()), ple); let user_level = get_user_power_level(event.sender(), auth_events); tracing::debug!( @@ -472,15 +465,14 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>, state: Membership /// Can this room federate based on its m.room.create event. pub fn can_federate(auth_events: &StateMap>) -> bool { - let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); + 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 == "true" @@ -698,7 +690,7 @@ pub fn can_federate(auth_events: &StateMap>) -> bool { /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { - let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); + 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) { @@ -714,7 +706,7 @@ pub fn get_named_level(auth_events: &StateMap>, name: &str, defa /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// object. pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { - if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { + if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { if let Ok(content) = pl.deserialize_content::() { if let Some(level) = content.users.get(user_id) { @@ -727,7 +719,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap() { if &c.creator == user_id { @@ -779,7 +771,7 @@ pub fn get_send_level( /// Check user can send invite. pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap>) -> Result { let user_level = get_user_power_level(event.sender, auth_events); - let key = (EventType::RoomPowerLevels, Some("".into())); + let key = (EventType::RoomPowerLevels, "".into()); let invite_level = auth_events .get(&key) .map_or_else( @@ -811,7 +803,7 @@ pub fn verify_third_party_invite( // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key() != Some(tp_id.signed.token.to_string()) { + if current_tpid.state_key() != tp_id.signed.token.to_string() { return false; } diff --git a/src/lib.rs b/src/lib.rs index 6004c3a8..3658640c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub use state_store::StateStore; const _YIELD_AFTER_ITERATIONS: usize = 100; /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, Option), T>; +pub type StateMap = BTreeMap<(EventType, String), T>; /// A mapping of `EventId` to `T`, usually a `StateEvent`. pub type EventMap = BTreeMap; @@ -185,7 +185,7 @@ impl StateResolution { .collect::>() ); - let power_event = resolved_control.get(&(EventType::RoomPowerLevels, Some("".into()))); + let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); tracing::debug!("PL {:?}", power_event); @@ -512,7 +512,7 @@ impl StateResolution { for key in event_auth::auth_types_for_event( event.kind(), event.sender(), - event.state_key(), + Some(event.state_key()), event.content().clone(), ) { if let Some(ev_id) = resolved_state.get(&key) { diff --git a/src/state_event.rs b/src/state_event.rs index 71032b29..bfbf2c72 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -34,7 +34,7 @@ impl StateEvent { prev_event_ids: self.prev_event_ids(), room_id: self.room_id().unwrap(), content: self.content(), - state_key: self.state_key(), + state_key: Some(self.state_key()), sender: self.sender(), } } @@ -175,7 +175,7 @@ impl StateEvent { }, } } - pub fn state_key(&self) -> Option { + pub fn state_key(&self) -> String { match self { Self::Full(ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), @@ -185,7 +185,7 @@ impl StateEvent { PduStub::RoomV1PduStub(ev) => ev.state_key.clone(), PduStub::RoomV3PduStub(ev) => ev.state_key.clone(), }, - } + }.expect("All state events have a state key") } #[cfg(not(feature = "unstable-pre-spec"))] From ee6aa356128423c4c04292f54d5280e8acb4abc6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 2 Oct 2020 17:53:03 -0400 Subject: [PATCH 056/130] StateEvent's event_id method must return owned EventId --- Cargo.toml | 15 ++++++++++----- src/lib.rs | 4 ++-- src/state_event.rs | 38 ++++++++++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23d9af1b..0f151557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,15 +22,20 @@ maplit = "1.0.2" thiserror = "1.0.20" tracing-subscriber = "0.2.11" +[dependencies.ruma] +git = "https://github.com/ruma/ruma" +rev = "648c3f5732db7524d967e472ec587fd33fa992e9" +features = ["client-api", "federation-api", "appservice-api"] + #[dependencies.ruma] #path = "../ruma/ruma" #features = ["client-api", "federation-api", "appservice-api"] -[dependencies.ruma] -git = "https://github.com/timokoesters/ruma" -branch = "timo-fed-fixes" -#rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" -features = ["client-api", "federation-api", "appservice-api"] +# [dependencies.ruma] +# git = "https://github.com/timokoesters/ruma" +# branch = "timo-fed-fixes" +# #rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" +# features = ["client-api", "federation-api", "appservice-api"] [features] unstable-pre-spec = ["ruma/unstable-pre-spec"] diff --git a/src/lib.rs b/src/lib.rs index 3658640c..e07bf2e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id().clone(), ev))); + event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -338,7 +338,7 @@ impl StateResolution { // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, *ev.origin_server_ts(), ev.event_id().clone()) + (-*pl, *ev.origin_server_ts(), ev.event_id()) }) } diff --git a/src/state_event.rs b/src/state_event.rs index bfbf2c72..ad1a0594 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -9,7 +9,7 @@ use ruma::{ }, EventId, RoomId, RoomVersionId, ServerName, UserId, }; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::time::SystemTime; pub struct Requester<'a> { @@ -118,11 +118,21 @@ impl StateEvent { }, } } - pub fn event_id(&self) -> &EventId { + pub fn event_id(&self) -> EventId { + use std::convert::TryFrom; + match self { Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.event_id, - Pdu::RoomV3Pdu(ev) => ev.event_id.as_ref().expect("RoomV3Pdu did not have an event id"), + Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), + Pdu::RoomV3Pdu(ev) => { + let value = serde_json::to_value(ev).expect("all ruma pdus are json values"); + EventId::try_from(&*format!( + "${}", + ruma::signatures::reference_hash(&value, &self.room_version()) + .expect("ruma can calculate reference hashes") + )) + .expect("ruma's reference hashes are valid event ids") + } }, Self::Sync(_ev) => panic!("Stubs don't have an event id"), } @@ -337,3 +347,23 @@ impl StateEvent { } } } + +// fn process_incoming_pdu( +// pdu: &ruma::Raw, +// version: &ruma::RoomVersionId, +// ) -> (EventId, serde_json::Value) { +// let mut value = serde_json::to_value(pdu.json().get()).expect("all ruma pdus are json values"); +// let event_id = EventId::try_from(&*format!( +// "${}", +// ruma::signatures::reference_hash(&value, version) +// .expect("ruma can calculate reference hashes") +// )) +// .expect("ruma's reference hashes are valid event ids"); + +// value +// .as_object_mut() +// .expect("ruma pdus are json objects") +// .insert("event_id".to_owned(), event_id.to_string().into()); + +// (event_id, value) +// } From d19c0d412954941399d8354845cff761d5ad50fe Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 2 Oct 2020 19:55:19 -0400 Subject: [PATCH 057/130] Rebase with timos changes and update/pin ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0f151557..f341ec18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ tracing-subscriber = "0.2.11" [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "648c3f5732db7524d967e472ec587fd33fa992e9" +rev = "8049631827ab8557cb1010d195dd6468f90effa8" features = ["client-api", "federation-api", "appservice-api"] #[dependencies.ruma] From 11e8856a91e98d152abb4e3bd1e59e8ede5fb9d7 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 25 Oct 2020 06:53:53 -0400 Subject: [PATCH 058/130] Fix tests after state_key = String from Option --- benches/state_res_bench.rs | 22 +++++++------- src/event_auth.rs | 11 +++---- src/lib.rs | 4 +-- tests/event_sorting.rs | 6 ++-- tests/res_with_auth_ids.rs | 41 +++++++++++++++----------- tests/state_res.rs | 60 ++++++++++++++++++++------------------ 6 files changed, 76 insertions(+), 68 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 1a7cd76b..d7027818 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -1,9 +1,10 @@ -// `cargo bench` works, but if you use `cargo bench -- --save-baseline ` +// Because of criterion `cargo bench` works, +// but if you use `cargo bench -- --save-baseline ` // or pass any other args to it, it fails with the error // `cargo bench unknown option --save-baseline`. // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. -use std::{collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH, sync::Arc}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, time::UNIX_EPOCH}; use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; @@ -150,8 +151,7 @@ impl TestStore { &[], )); let cre = create_event.event_id(); - self.0 - .insert(cre.clone(), Arc::clone(&create_event)); + self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( "IMA", @@ -162,8 +162,7 @@ impl TestStore { &[cre.clone()], &[cre.clone()], ); - self.0 - .insert(alice_mem.event_id(), Arc::clone(&alice_mem)); + self.0.insert(alice_mem.event_id(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -188,8 +187,7 @@ impl TestStore { &[cre.clone(), join_rules.event_id()], &[join_rules.event_id()], ); - self.0 - .insert(bob_mem.event_id(), Arc::clone(&bob_mem)); + self.0.insert(bob_mem.event_id(), Arc::clone(&bob_mem)); let charlie_mem = to_pdu_event( "IMC", @@ -420,8 +418,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "START", charlie(), - EventType::RoomMessage, - None, + EventType::RoomTopic, + Some(""), json!({}), &[], &[], @@ -429,8 +427,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "END", charlie(), - EventType::RoomMessage, - None, + EventType::RoomTopic, + Some(""), json!({}), &[], &[], diff --git a/src/event_auth.rs b/src/event_auth.rs index 7a62b6a2..a2a27005 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -87,7 +87,7 @@ pub fn auth_check( // TODO do_size_check is false when called by `iterative_auth_check` // do_size_check is also mostly accomplished by ruma with the exception of checking event_type, - // state_key, and json are below a certain size (255 and 65536 respectively) + // state_key, and json are below a certain size (255 and 65_536 respectively) // Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules // @@ -180,9 +180,7 @@ pub fn auth_check( // } // If sender's domain doesn't matches state_key, reject - if incoming_event.state_key() - != incoming_event.sender().server_name().as_str() - { + if incoming_event.state_key() != incoming_event.sender().server_name().as_str() { tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -769,7 +767,10 @@ pub fn get_send_level( } /// Check user can send invite. -pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap>) -> Result { +pub fn can_send_invite( + event: &Requester<'_>, + auth_events: &StateMap>, +) -> Result { let user_level = get_user_power_level(event.sender, auth_events); let key = (EventType::RoomPowerLevels, "".into()); let invite_level = auth_events diff --git a/src/lib.rs b/src/lib.rs index e07bf2e8..59c2fc8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -679,11 +679,11 @@ impl StateResolution { return Ok(*depth); } - dbg!(&sort_ev); + // dbg!(&sort_ev); let auth_events = sort_ev.auth_events(); event = None; for aid in auth_events { - dbg!(&aid); + // dbg!(&aid); let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store) .ok_or(Error::NotFound("Auth event not found".to_owned()))?; if aev.is_type_and_key(EventType::RoomPowerLevels, "") { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index e4ac5b44..f3e981e3 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -218,8 +218,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "END", charlie(), - EventType::RoomMessage, - None, + EventType::RoomTopic, + Some(""), json!({}), &[], &[], @@ -285,7 +285,7 @@ fn test_event_sort() { shuffle(&mut events_to_sort); - let power_level = resolved_power.get(&(EventType::RoomPowerLevels, Some("".into()))); + let power_level = resolved_power.get(&(EventType::RoomPowerLevels, "".into())); let sorted_event_ids = state_res::StateResolution::mainline_sort( &room_id(), diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 0787eb7a..c88fd66a 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -1,6 +1,6 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call)] -use std::{collections::BTreeMap, convert::TryFrom, sync::Once, time::UNIX_EPOCH, sync::Arc}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, sync::Once, time::UNIX_EPOCH}; use ruma::{ events::{ @@ -21,7 +21,11 @@ static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; -fn do_check(events: &[Arc], edges: Vec>, expected_state_ids: Vec) { +fn do_check( + events: &[Arc], + edges: Vec>, + expected_state_ids: Vec, +) { // to activate logging use `RUST_LOG=debug cargo t` let _ = LOGGER.call_once(|| { tracer::fmt() @@ -115,17 +119,17 @@ fn do_check(events: &[Arc], edges: Vec>, expected_state let mut state_after = state_before.clone(); - if fake_event.state_key().is_some() { - let ty = fake_event.kind().clone(); - // we know there is a state_key unwrap OK - let key = fake_event.state_key().clone(); - state_after.insert((ty, key), event_id.clone()); - } + // if fake_event.state_key().is_some() { + let ty = fake_event.kind().clone(); + // we know there is a state_key unwrap OK + let key = fake_event.state_key().clone(); + state_after.insert((ty, key), event_id.clone()); + // } let auth_types = state_res::auth_types_for_event( fake_event.kind(), fake_event.sender(), - fake_event.state_key(), + Some(fake_event.state_key()), fake_event.content().clone(), ); @@ -144,7 +148,7 @@ fn do_check(events: &[Arc], edges: Vec>, expected_state &e.event_id().to_string(), e.sender().clone(), e.kind(), - e.state_key().as_deref(), + Some(e.state_key()).as_deref(), e.content().clone(), &auth_events, prev_events, @@ -400,8 +404,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "START", charlie(), - EventType::RoomMessage, - None, + EventType::RoomTopic, + Some(""), json!({}), &[], &[], @@ -409,8 +413,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "END", charlie(), - EventType::RoomMessage, - None, + EventType::RoomTopic, + Some(""), json!({}), &[], &[], @@ -484,7 +488,7 @@ fn ban_with_auth_chains() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA", "MB"] + let expected_state_ids = vec!["PA", "MB", "END"] .into_iter() .map(event_id) .collect::>(); @@ -523,7 +527,7 @@ fn base_with_auth_chains() { "END", ]; for id in expected.iter().map(|i| event_id(i)) { - // make sure our resolved events are equall to the expected list + // make sure our resolved events are equal to the expected list assert!(resolved.iter().any(|eid| eid == &id), "{}", id) } assert_eq!(expected.len(), resolved.len()) @@ -641,7 +645,10 @@ fn join_rule_with_auth_chain() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["JR"].into_iter().map(event_id).collect::>(); + let expected_state_ids = vec!["JR", "END"] + .into_iter() + .map(event_id) + .collect::>(); do_check( &join_rule.values().cloned().collect::>(), diff --git a/tests/state_res.rs b/tests/state_res.rs index 6d72617e..116a0b36 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH, sync::Arc}; +use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, time::UNIX_EPOCH}; use maplit::btreemap; use ruma::{ @@ -262,8 +262,8 @@ fn INITIAL_EVENTS() -> BTreeMap> { Some(zera().to_string().as_str()), member_content_join(), ), - to_init_pdu_event("START", zera(), EventType::RoomMessage, None, json!({})), - to_init_pdu_event("END", zera(), EventType::RoomMessage, None, json!({})), + to_init_pdu_event("START", zera(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event("END", zera(), EventType::RoomTopic, Some(""), json!({})), ] .into_iter() .map(|ev| (ev.event_id(), ev)) @@ -280,7 +280,11 @@ fn INITIAL_EDGES() -> Vec { .collect::>() } -fn do_check(events: &[Arc], edges: Vec>, expected_state_ids: Vec) { +fn do_check( + events: &[Arc], + edges: Vec>, + expected_state_ids: Vec, +) { // to activate logging use `RUST_LOG=debug cargo t one_test_only` let _ = LOGGER.call_once(|| { tracer::fmt() @@ -380,17 +384,17 @@ fn do_check(events: &[Arc], edges: Vec>, expected_state let mut state_after = state_before.clone(); - if fake_event.state_key().is_some() { - let ty = fake_event.kind().clone(); - // we know there is a state_key unwrap OK - let key = fake_event.state_key().clone(); - state_after.insert((ty, key), event_id.clone()); - } + // if fake_event.state_key().is_some() { + let ty = fake_event.kind().clone(); + // we know there is a state_key unwrap OK + let key = fake_event.state_key().clone(); + state_after.insert((ty, key), event_id.clone()); + // } let auth_types = state_res::auth_types_for_event( fake_event.kind(), fake_event.sender(), - fake_event.state_key(), + Some(fake_event.state_key()), fake_event.content().clone(), ); @@ -409,7 +413,7 @@ fn do_check(events: &[Arc], edges: Vec>, expected_state &e.event_id().to_string(), e.sender().clone(), e.kind(), - e.state_key().as_deref(), + Some(e.state_key()).as_deref(), e.content().clone(), &auth_events, prev_events, @@ -498,7 +502,7 @@ fn ban_vs_power_level() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA", "MA", "MB"] + let expected_state_ids = vec!["PA", "MA", "MB", "END"] .into_iter() .map(event_id) .collect::>(); @@ -543,7 +547,7 @@ fn topic_basic() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA2", "T2"] + let expected_state_ids = vec!["PA2", "T2", "END"] .into_iter() .map(event_id) .collect::>(); @@ -580,7 +584,7 @@ fn topic_reset() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["T1", "MB", "PA"] + let expected_state_ids = vec!["T1", "MB", "PA", "END"] .into_iter() .map(event_id) .collect::>(); @@ -612,7 +616,7 @@ fn join_rule_evasion() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec![event_id("JR")]; + let expected_state_ids = vec![event_id("JR"), event_id("END")]; do_check(events, edges, expected_state_ids) } @@ -648,7 +652,10 @@ fn offtopic_power_level() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PC"].into_iter().map(event_id).collect::>(); + let expected_state_ids = vec!["PC", "END"] + .into_iter() + .map(event_id) + .collect::>(); do_check(events, edges, expected_state_ids) } @@ -680,7 +687,7 @@ fn topic_setting() { json!({"users": {alice(): 100, bob(): 50}}), ), to_init_pdu_event("T3", bob(), EventType::RoomTopic, Some(""), json!({})), - to_init_pdu_event("MZ1", zera(), EventType::RoomMessage, None, json!({})), + to_init_pdu_event("MZ1", zera(), EventType::RoomTopic, Some(""), json!({})), to_init_pdu_event("T4", alice(), EventType::RoomTopic, Some(""), json!({})), ]; @@ -692,7 +699,7 @@ fn topic_setting() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["T4", "PA2"] + let expected_state_ids = vec!["T4", "PA2", "END"] .into_iter() .map(event_id) .collect::>(); @@ -778,8 +785,7 @@ impl TestStore { &[], ); let cre = create_event.event_id(); - self.0 - .insert(cre.clone(), Arc::clone(&create_event)); + self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( "IMA", @@ -790,8 +796,7 @@ impl TestStore { &[cre.clone()], &[cre.clone()], ); - self.0 - .insert(alice_mem.event_id(), Arc::clone(&alice_mem)); + self.0.insert(alice_mem.event_id(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -802,8 +807,7 @@ impl TestStore { &[cre.clone(), alice_mem.event_id()], &[alice_mem.event_id()], ); - self.0 - .insert(join_rules.event_id(), join_rules.clone()); + self.0.insert(join_rules.event_id(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -816,8 +820,7 @@ impl TestStore { &[cre.clone(), join_rules.event_id()], &[join_rules.event_id()], ); - self.0 - .insert(bob_mem.event_id(), bob_mem.clone()); + self.0.insert(bob_mem.event_id(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -828,8 +831,7 @@ impl TestStore { &[cre, join_rules.event_id()], &[join_rules.event_id()], ); - self.0 - .insert(charlie_mem.event_id(), charlie_mem.clone()); + self.0.insert(charlie_mem.event_id(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() From 26b0f738a5f4ed07a11d5d3c8e7953b2a88b7df6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 27 Oct 2020 18:44:29 -0400 Subject: [PATCH 059/130] Fix unstable-pre-spec for federation membership join --- Cargo.toml | 7 ++++--- src/state_event.rs | 10 ++++++++-- src/state_store.rs | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f341ec18..b0f416ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,10 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.11" [dependencies.ruma] -git = "https://github.com/ruma/ruma" -rev = "8049631827ab8557cb1010d195dd6468f90effa8" -features = ["client-api", "federation-api", "appservice-api"] +# git = "https://github.com/ruma/ruma" +path = "../__forks__/ruma/ruma" +# rev = "64b9c646d15a359d62ab464a95176ff94adb2554" +features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] #path = "../ruma/ruma" diff --git a/src/state_event.rs b/src/state_event.rs index ad1a0594..fafbec53 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -125,7 +125,12 @@ impl StateEvent { Self::Full(ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), Pdu::RoomV3Pdu(ev) => { - let value = serde_json::to_value(ev).expect("all ruma pdus are json values"); + let mut value = serde_json::from_slice::>( + &serde_json::to_vec(ev).expect("all ruma pdus are json values"), + ) + .unwrap(); + value.remove("event_id"); + EventId::try_from(&*format!( "${}", ruma::signatures::reference_hash(&value, &self.room_version()) @@ -195,7 +200,8 @@ impl StateEvent { PduStub::RoomV1PduStub(ev) => ev.state_key.clone(), PduStub::RoomV3PduStub(ev) => ev.state_key.clone(), }, - }.expect("All state events have a state key") + } + .expect("All state events have a state key") } #[cfg(not(feature = "unstable-pre-spec"))] diff --git a/src/state_store.rs b/src/state_store.rs index c1695fa4..2f18692e 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -31,7 +31,7 @@ pub trait StateStore { result.push(ev_id.clone()); - let event = self.get_event(room_id, &ev_id).unwrap(); + let event = self.get_event(room_id, &ev_id)?; stack.extend(event.auth_events()); } From c3ba1e33eb26a7f2e99ddf300041e5f5e81023c3 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 27 Oct 2020 18:46:32 -0400 Subject: [PATCH 060/130] Use unstable-join branch of ruma from my fork --- Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0f416ad..71dd3f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,9 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.11" [dependencies.ruma] -# git = "https://github.com/ruma/ruma" -path = "../__forks__/ruma/ruma" +git = "https://github.com/DevinR528/ruma" +branch = "unstable-join" +# path = "../__forks__/ruma/ruma" # rev = "64b9c646d15a359d62ab464a95176ff94adb2554" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] From ac9282add6fc131e7fbb0e21b628dc05ae5c5749 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 27 Oct 2020 20:22:13 -0400 Subject: [PATCH 061/130] Allow get_power_level_for_sender to soft fail when eventId not found Hardcode RoomVersion6 into the eventId hashing --- src/lib.rs | 8 ++++++-- src/state_event.rs | 8 +++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 59c2fc8a..7e797091 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -429,7 +429,11 @@ impl StateResolution { // TODO store.auth_event_ids returns "self" with the event ids is this ok // event.auth_event_ids does not include its own event id ? - for aid in event.as_ref().unwrap().auth_events() { + for aid in event + .as_ref() + .map(|pdu| pdu.auth_events()) + .unwrap_or_default() + { if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { if aev.is_type_and_key(EventType::RoomPowerLevels, "") { pl = Some(aev); @@ -685,7 +689,7 @@ impl StateResolution { for aid in auth_events { // dbg!(&aid); let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store) - .ok_or(Error::NotFound("Auth event not found".to_owned()))?; + .ok_or_else(|| Error::NotFound("Auth event not found".to_owned()))?; if aev.is_type_and_key(EventType::RoomPowerLevels, "") { event = Some(aev); break; diff --git a/src/state_event.rs b/src/state_event.rs index fafbec53..92cdd367 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -129,11 +129,12 @@ impl StateEvent { &serde_json::to_vec(ev).expect("all ruma pdus are json values"), ) .unwrap(); + value.remove("event_id"); EventId::try_from(&*format!( "${}", - ruma::signatures::reference_hash(&value, &self.room_version()) + ruma::signatures::reference_hash(&value, &RoomVersionId::Version6) .expect("ruma can calculate reference hashes") )) .expect("ruma's reference hashes are valid event ids") @@ -341,14 +342,15 @@ impl StateEvent { /// Currently either version 1 or 3 is returned, 3 represents /// version 3 and above. pub fn room_version(&self) -> RoomVersionId { + // TODO: We have to know the actual room version this is not sufficient match self { Self::Full(ev) => match ev { Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, - Pdu::RoomV3Pdu(_) => RoomVersionId::Version3, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, }, Self::Sync(ev) => match ev { PduStub::RoomV1PduStub(_) => RoomVersionId::Version1, - PduStub::RoomV3PduStub(_) => RoomVersionId::Version3, + PduStub::RoomV3PduStub(_) => RoomVersionId::Version6, }, } } From 420b7c00e84cead9e61514701bad79f92c641d11 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 27 Oct 2020 20:23:26 -0400 Subject: [PATCH 062/130] Fix travis CI test by turning on features --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8f14fa53..faafcf4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ rust: - stable - nightly script: - - cargo test --all + - cargo test --features unstable-pre-spec From 6661771b31b97a38cfdc1830c9690c95f43d0ff0 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 1 Nov 2020 18:47:48 -0500 Subject: [PATCH 063/130] StateEvent now holds EventId in variant + custom ser/de --- Cargo.toml | 7 +- rustfmt.toml | 1 + src/event_auth.rs | 2 +- src/state_event.rs | 279 ++++++++++++++++++++++++++++++--------------- 4 files changed, 196 insertions(+), 93 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index 71dd3f53..8623837b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,10 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.11" [dependencies.ruma] -git = "https://github.com/DevinR528/ruma" -branch = "unstable-join" +git = "https://github.com/ruma/ruma" +# branch = "dev/unstable-join" # path = "../__forks__/ruma/ruma" -# rev = "64b9c646d15a359d62ab464a95176ff94adb2554" +rev = "c15382ca41262058302959eac4029ab4a1ea5889" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] @@ -40,6 +40,7 @@ features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec # features = ["client-api", "federation-api", "appservice-api"] [features] +default = ["unstable-pre-spec"] unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..2461f065 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true \ No newline at end of file diff --git a/src/event_auth.rs b/src/event_auth.rs index a2a27005..056ab90b 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -804,7 +804,7 @@ pub fn verify_third_party_invite( // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key() != tp_id.signed.token.to_string() { + if current_tpid.state_key() != tp_id.signed.token { return false; } diff --git a/src/state_event.rs b/src/state_event.rs index 92cdd367..afa1ba3c 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,16 +1,124 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, time::SystemTime}; use js_int::UInt; use ruma::{ events::{ + from_raw_json_value, pdu::{EventHash, Pdu, PduStub}, room::member::{MemberEventContent, MembershipState}, - EventType, + EventDeHelper, EventType, }, + serde::CanonicalJsonValue, + signatures::reference_hash, EventId, RoomId, RoomVersionId, ServerName, UserId, }; -use serde::{Deserialize, Serialize}; -use std::time::SystemTime; +use serde::{de, ser, Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct EventIdHelper { + event_id: EventId, +} + +#[derive(Clone, Debug)] +pub enum StateEvent { + Full(EventId, Pdu), + Stub(PduStub), +} + +impl Serialize for StateEvent { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use ser::Error; + use std::convert::TryInto; + + match self { + Self::Full(_id, ev) => { + // TODO: do we want to add the eventId when we + // serialize + let val: CanonicalJsonValue = serde_json::to_value(ev) + .map_err(S::Error::custom)? + .try_into() + .map_err(S::Error::custom)?; + // let val = val + // .as_object_mut() + // .ok_or_else(|| S::Error::custom("PDU is not an object"))?; + // val.insert("event_id".into(), serde_json::json!(_id)); + + match val { + CanonicalJsonValue::Object(obj) => obj.serialize(serializer), + _ => panic!("Pdu not an object"), + } + } + Self::Stub(_) => panic!("Found PduStub"), + } + } +} + +impl<'de> de::Deserialize<'de> for StateEvent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { + room_id, unsigned, .. + } = from_raw_json_value(&json)?; + + // TODO: do we even want to try for the existing ID + + // Determine whether the event is a full or stub + // based on the fields present. + Ok(if room_id.is_some() { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Full( + event_id(&json)?, + Pdu::RoomV3Pdu(from_raw_json_value(&json)?), + ), + } + } else { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Stub(from_raw_json_value(&json)?), + } + }) + } +} + +// #[cfg(not(test))] +// fn event_id(json: &RawJsonValue) -> Result { +// use std::convert::TryFrom; +// EventId::try_from(format!( +// "${}", +// reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) +// .map_err(de::Error::custom)?, +// )) +// .map_err(de::Error::custom) +// } + +// #[cfg(test)] +fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + Ok(match from_raw_json_value::(&json) { + Ok(id) => id.event_id, + Err(_) => { + // panic!("NOT DURING TESTS"); + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom)? + } + }) +} pub struct Requester<'a> { pub prev_event_ids: Vec, @@ -20,14 +128,6 @@ pub struct Requester<'a> { pub sender: &'a UserId, } -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum StateEvent { - Full(Pdu), - #[serde(skip_deserializing)] - Sync(PduStub), -} - impl StateEvent { pub fn to_requester(&self) -> Requester<'_> { Requester { @@ -41,7 +141,7 @@ impl StateEvent { pub fn is_power_event(&self) -> bool { match self { - Self::Full(any_event) => match any_event { + Self::Full(_, any_event) => match any_event { Pdu::RoomV1Pdu(event) => match event.kind { EventType::RoomPowerLevels | EventType::RoomJoinRules @@ -66,7 +166,7 @@ impl StateEvent { }, Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), }, - Self::Sync(any_event) => match any_event { + Self::Stub(any_event) => match any_event { PduStub::RoomV1PduStub(event) => match event.kind { EventType::RoomPowerLevels | EventType::RoomJoinRules @@ -96,11 +196,11 @@ impl StateEvent { &self, ) -> Result { match self { - Self::Full(ev) => match ev { + 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 { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => serde_json::from_value(ev.content.clone()), PduStub::RoomV3PduStub(ev) => serde_json::from_value(ev.content.clone()), }, @@ -108,49 +208,31 @@ impl StateEvent { } pub fn origin_server_ts(&self) -> &SystemTime { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.origin_server_ts, PduStub::RoomV3PduStub(ev) => &ev.origin_server_ts, }, } } pub fn event_id(&self) -> EventId { - use std::convert::TryFrom; - match self { - Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), - Pdu::RoomV3Pdu(ev) => { - let mut value = serde_json::from_slice::>( - &serde_json::to_vec(ev).expect("all ruma pdus are json values"), - ) - .unwrap(); - - value.remove("event_id"); - - EventId::try_from(&*format!( - "${}", - ruma::signatures::reference_hash(&value, &RoomVersionId::Version6) - .expect("ruma can calculate reference hashes") - )) - .expect("ruma's reference hashes are valid event ids") - } - }, - Self::Sync(_ev) => panic!("Stubs don't have an event id"), + // TODO; make this a &EventId + Self::Full(id, _) => id.clone(), + Self::Stub(_) => panic!("Stubs don't have an event id"), } } pub fn sender(&self) -> &UserId { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.sender, Pdu::RoomV3Pdu(ev) => &ev.sender, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.sender, PduStub::RoomV3PduStub(ev) => &ev.sender, }, @@ -159,11 +241,11 @@ impl StateEvent { pub fn redacts(&self) -> Option<&EventId> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(), Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.redacts.as_ref(), PduStub::RoomV3PduStub(ev) => ev.redacts.as_ref(), }, @@ -172,20 +254,20 @@ impl StateEvent { pub fn room_id(&self) -> Option<&RoomId> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => Some(&ev.room_id), Pdu::RoomV3Pdu(ev) => Some(&ev.room_id), }, - Self::Sync(_) => None, + Self::Stub(_) => None, } } pub fn kind(&self) -> EventType { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.kind.clone(), Pdu::RoomV3Pdu(ev) => ev.kind.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.kind.clone(), PduStub::RoomV3PduStub(ev) => ev.kind.clone(), }, @@ -193,11 +275,11 @@ impl StateEvent { } pub fn state_key(&self) -> String { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.state_key.clone(), PduStub::RoomV3PduStub(ev) => ev.state_key.clone(), }, @@ -208,11 +290,11 @@ impl StateEvent { #[cfg(not(feature = "unstable-pre-spec"))] pub fn origin(&self) -> String { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.origin.clone(), Pdu::RoomV3Pdu(ev) => ev.origin.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.origin.clone(), PduStub::RoomV3PduStub(ev) => ev.origin.clone(), }, @@ -221,11 +303,11 @@ impl StateEvent { pub fn prev_event_ids(&self) -> Vec { match self { - Self::Full(ev) => match ev { + 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 { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.prev_events.iter().map(|(id, _)| id).cloned().collect() } @@ -236,11 +318,11 @@ impl StateEvent { pub fn auth_events(&self) -> Vec { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.auth_events.iter().map(|(id, _)| id).cloned().collect() } @@ -251,11 +333,11 @@ impl StateEvent { pub fn content(&self) -> &serde_json::Value { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.content, Pdu::RoomV3Pdu(ev) => &ev.content, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.content, PduStub::RoomV3PduStub(ev) => &ev.content, }, @@ -263,14 +345,12 @@ impl StateEvent { } pub fn unsigned(&self) -> &BTreeMap { - // CONFIRM: The only way this would fail is if we got bad json, it should fail in ruma - // before it fails here. match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.unsigned, Pdu::RoomV3Pdu(ev) => &ev.unsigned, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.unsigned, PduStub::RoomV3PduStub(ev) => &ev.unsigned, }, @@ -279,11 +359,11 @@ impl StateEvent { pub fn signatures(&self) -> BTreeMap, BTreeMap> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.signatures.clone(), PduStub::RoomV3PduStub(ev) => ev.signatures.clone(), }, @@ -292,11 +372,11 @@ impl StateEvent { pub fn hashes(&self) -> &EventHash { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.hashes, Pdu::RoomV3Pdu(ev) => &ev.hashes, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.hashes, PduStub::RoomV3PduStub(ev) => &ev.hashes, }, @@ -305,11 +385,11 @@ impl StateEvent { pub fn depth(&self) -> &UInt { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.depth, Pdu::RoomV3Pdu(ev) => &ev.depth, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.depth, PduStub::RoomV3PduStub(ev) => &ev.depth, }, @@ -318,7 +398,7 @@ impl StateEvent { pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } @@ -326,7 +406,7 @@ impl StateEvent { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } @@ -339,16 +419,16 @@ impl StateEvent { /// Returns the room version this event is formatted for. /// - /// Currently either version 1 or 3 is returned, 3 represents + /// Currently either version 1 or 6 is returned, 6 represents /// version 3 and above. pub fn room_version(&self) -> RoomVersionId { // TODO: We have to know the actual room version this is not sufficient match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(_) => RoomVersionId::Version1, PduStub::RoomV3PduStub(_) => RoomVersionId::Version6, }, @@ -356,22 +436,43 @@ impl StateEvent { } } -// fn process_incoming_pdu( -// pdu: &ruma::Raw, -// version: &ruma::RoomVersionId, -// ) -> (EventId, serde_json::Value) { -// let mut value = serde_json::to_value(pdu.json().get()).expect("all ruma pdus are json values"); -// let event_id = EventId::try_from(&*format!( -// "${}", -// ruma::signatures::reference_hash(&value, version) -// .expect("ruma can calculate reference hashes") -// )) -// .expect("ruma's reference hashes are valid event ids"); +#[cfg(test)] +mod test { + use super::*; -// value -// .as_object_mut() -// .expect("ruma pdus are json objects") -// .insert("event_id".to_owned(), event_id.to_string().into()); + #[test] + fn deserialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; -// (event_id, value) -// } + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + _ => panic!("Stub found"), + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + } + + #[test] + fn serialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; + + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + _ => panic!("Stub found"), + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + + // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing + assert_eq!( + ruma::serde::to_canonical_json_string(&pdu).unwrap(), + r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, + ) + } +} From ac4ab7ac06710610cbb351ad912fad05b24574e1 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 1 Nov 2020 18:48:23 -0500 Subject: [PATCH 064/130] Update tests to new StateEvent using Pdu::RoomV3Pdu --- tests/event_auth.rs | 33 ++++++++++++++++----------------- tests/event_sorting.rs | 17 ----------------- tests/res_with_auth_ids.rs | 24 ++++++------------------ tests/state_res.rs | 19 +------------------ 4 files changed, 23 insertions(+), 70 deletions(-) diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 7ed7d44a..fe3df512 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -2,7 +2,6 @@ use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use ruma::{ events::{ - pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -110,27 +109,27 @@ where .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) + // .map(|id| { + // ( + // id, + // EventHash { + // sha256: "hello".into(), + // }, + // ) + // }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) + // .map(|id| { + // ( + // id, + // EventHash { + // sha256: "hello".into(), + // }, + // ) + // }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index f3e981e3..8897a4a2 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -2,7 +2,6 @@ use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use ruma::{ events::{ - pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -92,27 +91,11 @@ where .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index c88fd66a..e5e22e1e 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -1,10 +1,14 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call)] -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, sync::Once, time::UNIX_EPOCH}; +use std::{ + collections::BTreeMap, + convert::TryFrom, + sync::{Arc, Once}, + time::UNIX_EPOCH, +}; use ruma::{ events::{ - pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -278,27 +282,11 @@ where .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/tests/state_res.rs b/tests/state_res.rs index 116a0b36..1609d377 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -3,7 +3,6 @@ use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, time::UNIX_EPOCH}; use maplit::btreemap; use ruma::{ events::{ - pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -97,27 +96,11 @@ where .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let json = if let Some(state_key) = state_key { @@ -496,7 +479,7 @@ fn ban_vs_power_level() { let edges = vec![ vec!["END", "MB", "MA", "PA", "START"], - vec!["END", "PB", "PA"], + vec!["END", "PA", "PB"], ] .into_iter() .map(|list| list.into_iter().map(event_id).collect::>()) From 5e7f60e5d9229b2d22f5cb51ad943a58e72dd60a Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 1 Nov 2020 18:52:50 -0500 Subject: [PATCH 065/130] Add gen-eventid feature to generate EventId every deserialize So we do not rely on any found EventId, this makes all the tests break. --- Cargo.toml | 1 + src/state_event.rs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8623837b..2c44e313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec [features] default = ["unstable-pre-spec"] +gen-eventid = [] unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] diff --git a/src/state_event.rs b/src/state_event.rs index afa1ba3c..330143d8 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -92,18 +92,18 @@ impl<'de> de::Deserialize<'de> for StateEvent { } } -// #[cfg(not(test))] -// fn event_id(json: &RawJsonValue) -> Result { -// use std::convert::TryFrom; -// EventId::try_from(format!( -// "${}", -// reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) -// .map_err(de::Error::custom)?, -// )) -// .map_err(de::Error::custom) -// } +#[cfg(feature = "gen-eventid")] +fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom) +} -// #[cfg(test)] +#[cfg(not(feature = "gen-eventid"))] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; Ok(match from_raw_json_value::(&json) { From d0d2f779986f1e2b96763c415fc794476d9d411d Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 2 Nov 2020 10:35:50 -0500 Subject: [PATCH 066/130] Add from_id_value associated method to create a StateEvent from id+json --- src/state_event.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/state_event.rs b/src/state_event.rs index 330143d8..71190a8f 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -35,20 +35,22 @@ impl Serialize for StateEvent { use std::convert::TryInto; match self { - Self::Full(_id, ev) => { + Self::Full(id, ev) => { // TODO: do we want to add the eventId when we // serialize let val: CanonicalJsonValue = serde_json::to_value(ev) .map_err(S::Error::custom)? .try_into() .map_err(S::Error::custom)?; - // let val = val - // .as_object_mut() - // .ok_or_else(|| S::Error::custom("PDU is not an object"))?; - // val.insert("event_id".into(), serde_json::json!(_id)); match val { - CanonicalJsonValue::Object(obj) => obj.serialize(serializer), + CanonicalJsonValue::Object(mut obj) => { + obj.insert( + "event_id".into(), + ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, + ); + obj.serialize(serializer) + } _ => panic!("Pdu not an object"), } } @@ -129,6 +131,13 @@ pub struct Requester<'a> { } impl StateEvent { + pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { + Ok(Self::Full( + id, + Pdu::RoomV3Pdu(serde_json::from_value(json)?), + )) + } + pub fn to_requester(&self) -> Requester<'_> { Requester { prev_event_ids: self.prev_event_ids(), From d5870c6cc0eeda2ba8a8c34023ace212c98cfa30 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 2 Nov 2020 14:01:47 -0500 Subject: [PATCH 067/130] Fix serialization test adding event_id field to the JSON string output --- src/state_event.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/state_event.rs b/src/state_event.rs index 71190a8f..1f86c000 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -94,6 +94,8 @@ impl<'de> de::Deserialize<'de> for StateEvent { } } +/// This feature is turned on in conduit but off when the tests run because +/// we rely on the EventId to check the state-res. #[cfg(feature = "gen-eventid")] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; @@ -105,6 +107,7 @@ fn event_id(json: &RawJsonValue) -> Result { .map_err(de::Error::custom) } +/// Only turned on for testing where we need to keep the ID. #[cfg(not(feature = "gen-eventid"))] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; @@ -479,9 +482,11 @@ mod test { ); // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing + // For testing we must serialize the PDU with the `event_id` field this is probably not correct for production + // although without them we get "Invalid bytes in DB" errors in conduit assert_eq!( ruma::serde::to_canonical_json_string(&pdu).unwrap(), - r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, + r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"event_id":"$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU","hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, ) } } From 471ae2cbb4a56904750a9ab46ebde928843c79f4 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 3 Nov 2020 18:49:29 -0500 Subject: [PATCH 068/130] Make from_id_value take 2 args not tuple --- src/state_event.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/state_event.rs b/src/state_event.rs index 1f86c000..477af7b5 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -20,6 +20,42 @@ struct EventIdHelper { event_id: EventId, } +#[cfg(feature = "gen-eventid")] +fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom) +} + +#[cfg(not(feature = "gen-eventid"))] +fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + Ok(match from_raw_json_value::(&json) { + Ok(id) => id.event_id, + Err(_) => { + // panic!("NOT DURING TESTS"); + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom)? + } + }) +} + +pub struct Requester<'a> { + pub prev_event_ids: Vec, + pub room_id: &'a RoomId, + pub content: &'a serde_json::Value, + pub state_key: Option, + pub sender: &'a UserId, +} + #[derive(Clone, Debug)] pub enum StateEvent { Full(EventId, Pdu), From f45cb2963a4fd9ac4f2f728ccb678a928cf5b850 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 3 Nov 2020 18:51:37 -0500 Subject: [PATCH 069/130] Fix formatting and grouping of state_event items --- src/state_event.rs | 42 +++--------------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/state_event.rs b/src/state_event.rs index 477af7b5..3c3471db 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -20,6 +20,8 @@ struct EventIdHelper { event_id: EventId, } +/// This feature is turned on in conduit but off when the tests run because +/// we rely on the EventId to check the state-res. #[cfg(feature = "gen-eventid")] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; @@ -31,6 +33,7 @@ fn event_id(json: &RawJsonValue) -> Result { .map_err(de::Error::custom) } +/// Only turned on for testing where we need to keep the ID. #[cfg(not(feature = "gen-eventid"))] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; @@ -130,45 +133,6 @@ impl<'de> de::Deserialize<'de> for StateEvent { } } -/// This feature is turned on in conduit but off when the tests run because -/// we rely on the EventId to check the state-res. -#[cfg(feature = "gen-eventid")] -fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom) -} - -/// Only turned on for testing where we need to keep the ID. -#[cfg(not(feature = "gen-eventid"))] -fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - Ok(match from_raw_json_value::(&json) { - Ok(id) => id.event_id, - Err(_) => { - // panic!("NOT DURING TESTS"); - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom)? - } - }) -} - -pub struct Requester<'a> { - pub prev_event_ids: Vec, - pub room_id: &'a RoomId, - pub content: &'a serde_json::Value, - pub state_key: Option, - pub sender: &'a UserId, -} - impl StateEvent { pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { Ok(Self::Full( From b04c74ce88ad2e57bfacffbe4280338c0e10652d Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 4 Nov 2020 15:19:38 -0500 Subject: [PATCH 070/130] Add more comments and cleanup out of date comments --- src/lib.rs | 1 + tests/state_res.rs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e797091..348af5eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,6 +185,7 @@ impl StateResolution { .collect::>() ); + // This "epochs" power level event let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); tracing::debug!("PL {:?}", power_event); diff --git a/tests/state_res.rs b/tests/state_res.rs index 1609d377..5b973685 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -367,12 +367,9 @@ fn do_check( let mut state_after = state_before.clone(); - // if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); - // we know there is a state_key unwrap OK let key = fake_event.state_key().clone(); state_after.insert((ty, key), event_id.clone()); - // } let auth_types = state_res::auth_types_for_event( fake_event.kind(), From db3f68626d5981ac2d430d8dbf008efeb7914f4e Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 30 Nov 2020 12:04:07 -0500 Subject: [PATCH 071/130] Add constructor from CanonicalJsonObject to StateEvent --- benches/state_res_bench.rs | 19 +------------------ rustfmt.toml | 2 +- src/state_event.rs | 11 +++++++++++ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index d7027818..ed2f8d47 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -10,7 +10,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; use ruma::{ events::{ - pdu::EventHash, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -51,7 +50,7 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { b.iter(|| { let _resolved = match StateResolution::resolve( &room_id(), - &RoomVersionId::Version2, + &RoomVersionId::Version6, &[state_at_bob.clone(), state_at_charlie.clone()], None, &store, @@ -299,27 +298,11 @@ where .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let prev_events = prev_events .iter() .map(AsRef::as_ref) .map(event_id) - .map(|id| { - ( - id, - EventHash { - sha256: "hello".into(), - }, - ) - }) .collect::>(); let json = if let Some(state_key) = state_key { diff --git a/rustfmt.toml b/rustfmt.toml index 2461f065..7d2cf549 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1 @@ -merge_imports = true \ No newline at end of file +merge_imports = true diff --git a/src/state_event.rs b/src/state_event.rs index 3c3471db..d709a19a 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -141,6 +141,17 @@ impl StateEvent { )) } + pub fn from_id_canon_obj( + id: EventId, + json: ruma::serde::CanonicalJsonObject, + ) -> Result { + Ok(Self::Full( + id, + // TODO: this is unfortunate (from_value(to_value(json)))... + Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), + )) + } + pub fn to_requester(&self) -> Requester<'_> { Requester { prev_event_ids: self.prev_event_ids(), From 0e9332c04f10d488e301c4023d81663f494b3036 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 30 Nov 2020 14:50:39 -0500 Subject: [PATCH 072/130] Add text file to keep track of benches from time to time --- benches/outcomes.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 benches/outcomes.txt diff --git a/benches/outcomes.txt b/benches/outcomes.txt new file mode 100644 index 00000000..cde1855e --- /dev/null +++ b/benches/outcomes.txt @@ -0,0 +1,17 @@ +11/29/2020 BRANCH: timo-spec-comp REV: d2a85669cc6056679ce6ca0fde4658a879ad2b08 +lexicographical topological sort + time: [1.7123 us 1.7157 us 1.7199 us] + change: [-1.7584% -1.5433% -1.3205%] (p = 0.00 < 0.05) + Performance has improved. +Found 8 outliers among 100 measurements (8.00%) + 2 (2.00%) low mild + 5 (5.00%) high mild + 1 (1.00%) high severe + +resolve state of 5 events one fork + time: [10.981 us 10.998 us 11.020 us] +Found 3 outliers among 100 measurements (3.00%) + 3 (3.00%) high mild + +resolve state of 10 events 3 conflicting + time: [26.858 us 26.946 us 27.037 us] \ No newline at end of file From 89cfb1967ab785afa283ca153ebbbc541e1534f6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 4 Dec 2020 18:13:46 -0500 Subject: [PATCH 073/130] Update ruma to latest, StateEvent is still enum without Stub --- Cargo.toml | 2 +- src/event_auth.rs | 4 +- src/state_event.rs | 121 ++++----------------------------------------- 3 files changed, 11 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c44e313..77241812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ tracing-subscriber = "0.2.11" git = "https://github.com/ruma/ruma" # branch = "dev/unstable-join" # path = "../__forks__/ruma/ruma" -rev = "c15382ca41262058302959eac4029ab4a1ea5889" +rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] diff --git a/src/event_auth.rs b/src/event_auth.rs index 056ab90b..c4e1b974 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -102,9 +102,7 @@ pub fn auth_check( } // If the domain of the room_id does not match the domain of the sender, reject - if incoming_event.room_id().map(|id| id.server_name()) - != Some(incoming_event.sender().server_name()) - { + if incoming_event.room_id().server_name() != incoming_event.sender().server_name() { tracing::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } diff --git a/src/state_event.rs b/src/state_event.rs index d709a19a..afea433a 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -4,7 +4,7 @@ use js_int::UInt; use ruma::{ events::{ from_raw_json_value, - pdu::{EventHash, Pdu, PduStub}, + pdu::{EventHash, Pdu}, room::member::{MemberEventContent, MembershipState}, EventDeHelper, EventType, }, @@ -62,7 +62,6 @@ pub struct Requester<'a> { #[derive(Clone, Debug)] pub enum StateEvent { Full(EventId, Pdu), - Stub(PduStub), } impl Serialize for StateEvent { @@ -93,7 +92,6 @@ impl Serialize for StateEvent { _ => panic!("Pdu not an object"), } } - Self::Stub(_) => panic!("Found PduStub"), } } } @@ -123,12 +121,7 @@ impl<'de> de::Deserialize<'de> for StateEvent { ), } } else { - match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Stub(from_raw_json_value(&json)?), - } + panic!("Found stub event") }) } } @@ -155,7 +148,7 @@ impl StateEvent { pub fn to_requester(&self) -> Requester<'_> { Requester { prev_event_ids: self.prev_event_ids(), - room_id: self.room_id().unwrap(), + room_id: self.room_id(), content: self.content(), state_key: Some(self.state_key()), sender: self.sender(), @@ -189,30 +182,6 @@ impl StateEvent { }, Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), }, - Self::Stub(any_event) => match any_event { - PduStub::RoomV1PduStub(event) => match event.kind { - EventType::RoomPowerLevels - | EventType::RoomJoinRules - | EventType::RoomCreate => event.state_key == Some("".into()), - EventType::RoomMember => { - if let Ok(content) = - serde_json::from_value::(event.content.clone()) - { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() - // TODO does None here mean the same as state_key = "" - != event.state_key.as_deref().unwrap_or(""); - } - } - - false - } - _ => false, - }, - PduStub::RoomV3PduStub(event) => event.state_key == Some("".into()), - }, } } pub fn deserialize_content( @@ -223,10 +192,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()), Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()), }, - Self::Stub(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 { @@ -235,17 +200,12 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.origin_server_ts, - PduStub::RoomV3PduStub(ev) => &ev.origin_server_ts, - }, } } pub fn event_id(&self) -> EventId { match self { // TODO; make this a &EventId Self::Full(id, _) => id.clone(), - Self::Stub(_) => panic!("Stubs don't have an event id"), } } @@ -255,10 +215,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.sender, Pdu::RoomV3Pdu(ev) => &ev.sender, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.sender, - PduStub::RoomV3PduStub(ev) => &ev.sender, - }, } } @@ -268,20 +224,15 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(), Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.redacts.as_ref(), - PduStub::RoomV3PduStub(ev) => ev.redacts.as_ref(), - }, } } - pub fn room_id(&self) -> Option<&RoomId> { + pub fn room_id(&self) -> &RoomId { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => Some(&ev.room_id), - Pdu::RoomV3Pdu(ev) => Some(&ev.room_id), + Pdu::RoomV1Pdu(ev) => &ev.room_id, + Pdu::RoomV3Pdu(ev) => &ev.room_id, }, - Self::Stub(_) => None, } } pub fn kind(&self) -> EventType { @@ -290,10 +241,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.kind.clone(), Pdu::RoomV3Pdu(ev) => ev.kind.clone(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.kind.clone(), - PduStub::RoomV3PduStub(ev) => ev.kind.clone(), - }, } } pub fn state_key(&self) -> String { @@ -302,10 +249,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.state_key.clone(), - PduStub::RoomV3PduStub(ev) => ev.state_key.clone(), - }, } .expect("All state events have a state key") } @@ -317,10 +260,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.origin.clone(), Pdu::RoomV3Pdu(ev) => ev.origin.clone(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.origin.clone(), - PduStub::RoomV3PduStub(ev) => ev.origin.clone(), - }, } } @@ -330,12 +269,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => { - ev.prev_events.iter().map(|(id, _)| id).cloned().collect() - } - PduStub::RoomV3PduStub(ev) => ev.prev_events.to_vec(), - }, } } @@ -345,12 +278,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => { - ev.auth_events.iter().map(|(id, _)| id).cloned().collect() - } - PduStub::RoomV3PduStub(ev) => ev.auth_events.to_vec(), - }, } } @@ -360,10 +287,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.content, Pdu::RoomV3Pdu(ev) => &ev.content, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.content, - PduStub::RoomV3PduStub(ev) => &ev.content, - }, } } @@ -373,23 +296,17 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.unsigned, Pdu::RoomV3Pdu(ev) => &ev.unsigned, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.unsigned, - PduStub::RoomV3PduStub(ev) => &ev.unsigned, - }, } } - pub fn signatures(&self) -> BTreeMap, BTreeMap> { + pub fn signatures( + &self, + ) -> BTreeMap, BTreeMap> { match self { Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => ev.signatures.clone(), - PduStub::RoomV3PduStub(ev) => ev.signatures.clone(), - }, } } @@ -399,10 +316,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.hashes, Pdu::RoomV3Pdu(ev) => &ev.hashes, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.hashes, - PduStub::RoomV3PduStub(ev) => &ev.hashes, - }, } } @@ -412,10 +325,6 @@ impl StateEvent { Pdu::RoomV1Pdu(ev) => &ev.depth, Pdu::RoomV3Pdu(ev) => &ev.depth, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(ev) => &ev.depth, - PduStub::RoomV3PduStub(ev) => &ev.depth, - }, } } @@ -429,14 +338,6 @@ impl StateEvent { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } }, - Self::Stub(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) - } - }, } } @@ -451,10 +352,6 @@ impl StateEvent { Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, }, - Self::Stub(ev) => match ev { - PduStub::RoomV1PduStub(_) => RoomVersionId::Version1, - PduStub::RoomV3PduStub(_) => RoomVersionId::Version6, - }, } } } From 6c26da97a65f0173f21f9df1bde15f3cc9fc5cae Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 5 Dec 2020 15:47:21 -0500 Subject: [PATCH 074/130] Bump deps and remove js_int now imported from ruma --- Cargo.toml | 12 +++++------- src/event_auth.rs | 4 ++-- src/state_event.rs | 6 ++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 77241812..8d9c8ec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,13 @@ license = "MIT" readme = "README.md" repository = "https://github.com/ruma/state-res" -# 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.9" -serde = { version = "1.0.115", features = ["derive"] } -serde_json = "1.0.57" -tracing = "0.1.19" +serde = { version = "1.0.117", features = ["derive"] } +serde_json = "1.0.60" +tracing = "0.1.22" maplit = "1.0.2" -thiserror = "1.0.20" -tracing-subscriber = "0.2.11" +thiserror = "1.0.22" [dependencies.ruma] git = "https://github.com/ruma/ruma" @@ -47,6 +44,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] criterion = "0.3.3" rand = "0.7.3" +tracing-subscriber = "0.2.15" [[bench]] name = "state_res_bench" diff --git a/src/event_auth.rs b/src/event_auth.rs index c4e1b974..ee57965f 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -748,7 +748,7 @@ pub fn get_send_level( .events .get(&e_type) .cloned() - .unwrap_or_else(|| js_int::int!(50)) + .unwrap_or_else(|| ruma::int!(50)) .into(); let state_def: i64 = content.state_default.into(); let event_def: i64 = content.events_default.into(); @@ -774,7 +774,7 @@ pub fn can_send_invite( let invite_level = auth_events .get(&key) .map_or_else( - || Ok::<_, Error>(js_int::int!(50)), + || Ok::<_, Error>(ruma::int!(50)), |power_levels| { power_levels .deserialize_content::() diff --git a/src/state_event.rs b/src/state_event.rs index afea433a..141b896a 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,6 +1,5 @@ use std::{collections::BTreeMap, time::SystemTime}; -use js_int::UInt; use ruma::{ events::{ from_raw_json_value, @@ -10,7 +9,7 @@ use ruma::{ }, serde::CanonicalJsonValue, signatures::reference_hash, - EventId, RoomId, RoomVersionId, ServerName, UserId, + EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, }; use serde::{de, ser, Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; @@ -59,6 +58,7 @@ pub struct Requester<'a> { pub sender: &'a UserId, } +// TODO: This no longer needs to be an enum now that PduStub is gone #[derive(Clone, Debug)] pub enum StateEvent { Full(EventId, Pdu), @@ -369,7 +369,6 @@ mod test { assert_eq!( match &pdu { StateEvent::Full(id, _) => id, - _ => panic!("Stub found"), }, &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") ); @@ -384,7 +383,6 @@ mod test { assert_eq!( match &pdu { StateEvent::Full(id, _) => id, - _ => panic!("Stub found"), }, &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") ); From ea7dc52daf084927d163b281a2194be78e701f96 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 7 Dec 2020 10:46:14 -0500 Subject: [PATCH 075/130] Bump ruma to be even with conduit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8d9c8ec5..93b36356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ thiserror = "1.0.22" git = "https://github.com/ruma/ruma" # branch = "dev/unstable-join" # path = "../__forks__/ruma/ruma" -rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237" +rev = "ee814aa84934530d76f5e4b275d739805b49bdef" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] From c6a108631da0134a0af4e9b36ae627ff23ae3d8a Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 7 Dec 2020 16:51:31 -0500 Subject: [PATCH 076/130] Add apply_event function testing if a given event will pass auth --- src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/state_res.rs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 348af5eb..89c6cbf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,51 @@ pub type EventMap = BTreeMap; pub struct StateResolution; impl StateResolution { + /// Check if the `incoming_event` can be included in the given `current_state`. + /// + /// This will authenticate the event against the current state of the room. It + /// is important that the `current_state` argument is accurate and complete. + pub fn apply_event( + room_id: &RoomId, + room_version: &RoomVersionId, + incoming_event: Arc, + current_state: &StateMap, + event_map: Option>>, + store: &dyn StateStore, + ) -> Result { + tracing::info!("Applying a single event, state resolution starting"); + let ev = incoming_event; + + let mut event_map = if let Some(ev_map) = event_map { + ev_map + } else { + EventMap::new() + }; + let prev_event = if let Some(id) = ev.prev_event_ids().first() { + store.get_event(room_id, id).ok() + } else { + None + }; + + let mut auth_events = StateMap::new(); + for key in event_auth::auth_types_for_event( + ev.kind(), + ev.sender(), + Some(ev.state_key()), + ev.content().clone(), + ) { + if let Some(ev_id) = current_state.get(&key) { + if let Some(event) = + StateResolution::get_or_load_event(room_id, ev_id, &mut event_map, store) + { + // TODO synapse checks `rejected_reason` is None here + auth_events.insert(key.clone(), event); + } + } + } + + event_auth::auth_check(room_version, &ev, prev_event, auth_events, None) + } /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. /// diff --git a/tests/state_res.rs b/tests/state_res.rs index 5b973685..7b8110dd 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -354,7 +354,7 @@ fn do_check( let resolved = StateResolution::resolve( &room_id(), - &RoomVersionId::Version1, + &RoomVersionId::Version6, &state_sets, Some(event_map.clone()), &store, @@ -398,6 +398,35 @@ fn do_check( &auth_events, prev_events, ); + + // This can be used to sort of test this function + // match StateResolution::apply_event( + // &room_id(), + // &RoomVersionId::Version6, + // Arc::clone(&event), + // &state_after, + // Some(event_map.clone()), + // &store, + // ) { + // Ok(res) => { + // println!( + // "res contains: {} passed: {} for {}\n{:?}", + // state_after + // .get(&(event.kind(), event.state_key())) + // .map(|id| id == &ev_id) + // .unwrap_or_default(), + // res, + // event.event_id().as_str(), + // event + // .prev_event_ids() + // .iter() + // .map(|id| id.to_string()) + // .collect::>() + // ); + // } + // Err(e) => panic!("resolution for {} failed: {}", node, e), + // } + // we have to update our store, an actual user of this lib would // be giving us state from a DB. // From dca71f76eea9f1378a54e96e5fc98d71dfbf5dde Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 13 Dec 2020 09:12:14 -0500 Subject: [PATCH 077/130] Update readme example Option -> String --- README.md | 2 +- tests/state_res.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index d6f4df8a..91f28152 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ struct StateEvent { } /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, Option), T>; +pub type StateMap = BTreeMap<(EventType, String), T>; /// A mapping of `EventId` to `T`, usually a `StateEvent`. pub type EventMap = BTreeMap; diff --git a/tests/state_res.rs b/tests/state_res.rs index 7b8110dd..00efa3a4 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -429,10 +429,6 @@ fn do_check( // we have to update our store, an actual user of this lib would // be giving us state from a DB. - // - // TODO - // TODO we need to convert the `StateResolution::resolve` to use the event_map - // because the user of this crate cannot update their DB's state. store.0.insert(ev_id.clone(), Arc::clone(&event)); state_at_event.insert(node, state_after); From 55e889a11fe8472a6af667c9f4e4c48ef186f87a Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 14 Dec 2020 11:13:45 -0500 Subject: [PATCH 078/130] Add method to resolve batches of conflicted events --- src/lib.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 89c6cbf0..b7ad4585 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,135 @@ impl StateResolution { event_auth::auth_check(room_version, &ev, prev_event, auth_events, None) } + + pub fn resolve_incoming( + room_id: &RoomId, + room_version: &RoomVersionId, + unconflicted: &StateMap, + conflicted: Vec<((EventType, String), EventId)>, + event_map: Option>>, + store: &dyn StateStore, + ) -> Result> { + let mut event_map = if let Some(ev_map) = event_map { + ev_map + } else { + BTreeMap::new() + }; + + let all_ids = vec![ + unconflicted.values().cloned().collect(), + // conflicted can contain duplicates no BTreeMaps + conflicted.iter().map(|(_, v)| v).cloned().collect(), + ]; + // We use the store here since it takes a Vec instead of Vec like + // `StateResolution::get_auth_chain_diff` + let mut auth_diff = store.auth_chain_diff(room_id, all_ids)?; + + tracing::debug!("auth diff size {}", auth_diff.len()); + + // add the auth_diff to conflicting now we have a full set of conflicting events + auth_diff.extend(conflicted.iter().map(|(_, v)| v).cloned()); + let mut all_conflicted = auth_diff + .into_iter() + .collect::>() + .into_iter() + .collect::>(); + + tracing::info!("full conflicted set is {} events", all_conflicted.len()); + + // gather missing events for the event_map + let events = store + .get_events( + room_id, + &all_conflicted + .iter() + // we only want the events we don't know about yet + .filter(|id| !event_map.contains_key(id)) + .cloned() + .collect::>(), + ) + .unwrap(); + + // update event_map to include the fetched events + event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); + + // Don't honor events we cannot verify. + all_conflicted.retain(|id| event_map.contains_key(id)); + + // get only the control events with a state_key: "" or ban/kick event (sender != state_key) + let control_events = all_conflicted + .iter() + .filter(|id| is_power_event(id, &event_map)) + .cloned() + .collect::>(); + + // sort the control events based on power_level/clock/event_id and outgoing/incoming edges + let mut sorted_control_levels = StateResolution::reverse_topological_power_sort( + room_id, + &control_events, + &mut event_map, + store, + &all_conflicted, + ); + + // sequentially auth check each control event. + let resolved_control = StateResolution::iterative_auth_check( + room_id, + room_version, + &sorted_control_levels, + unconflicted, + &mut event_map, + store, + )?; + + // At this point the control_events have been resolved we now have to + // sort the remaining events using the mainline of the resolved power level. + sorted_control_levels.dedup(); + let deduped_power_ev = sorted_control_levels; + + // This removes the control events that passed auth and more importantly those that failed auth + let events_to_resolve = all_conflicted + .iter() + .filter(|id| !deduped_power_ev.contains(id)) + .cloned() + .collect::>(); + + // This "epochs" power level event + let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); + + tracing::debug!("PL {:?}", power_event); + + let sorted_left_events = StateResolution::mainline_sort( + room_id, + &events_to_resolve, + power_event, + &mut event_map, + store, + ); + + tracing::debug!( + "SORTED LEFT {:?}", + sorted_left_events + .iter() + .map(ToString::to_string) + .collect::>() + ); + + let mut resolved_state = StateResolution::iterative_auth_check( + room_id, + room_version, + &sorted_left_events, + &resolved_control, // the control events are added to the final resolved state + &mut event_map, + store, + )?; + + // add unconflicted state to the resolved state + resolved_state.extend(unconflicted.clone()); + + Ok(resolved_state) + } + /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. /// From 33bb319b4515249d98c12597e570885bc42a6159 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 14 Dec 2020 13:27:47 -0500 Subject: [PATCH 079/130] Fix failing tests because clean overwrites resolved_state in resolve fn --- src/lib.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b7ad4585..c835996c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,19 +196,20 @@ impl StateResolution { .collect::>() ); - let mut resolved_state = StateResolution::iterative_auth_check( + let resolved_state = StateResolution::iterative_auth_check( room_id, room_version, &sorted_left_events, - &resolved_control, // the control events are added to the final resolved state + &resolved_control, // The control events are added to the final resolved state &mut event_map, store, )?; - // add unconflicted state to the resolved state - resolved_state.extend(unconflicted.clone()); - - Ok(resolved_state) + // Add unconflicted state to the resolved state + // We do it this way so any resolved_state would overwrite unconflicted + let mut clean = unconflicted.clone(); + clean.extend(resolved_state); + Ok(clean) } /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph @@ -384,14 +385,15 @@ impl StateResolution { room_id, room_version, &sorted_left_events, - &resolved_control, // the control events are added to the final resolved state + &resolved_control, // The control events are added to the final resolved state &mut event_map, store, )?; // add unconflicted state to the resolved state + // TODO: + // CLEAN OVERWRITES ANY DUPLICATE KEYS FROM RESOLVED STATE resolved_state.extend(clean); - Ok(resolved_state) } From dd2a115b94de2add57ca57682118be7677ebcb19 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 14 Dec 2020 13:28:28 -0500 Subject: [PATCH 080/130] Clean up tests, move setup into its own file --- tests/event_auth.rs | 234 +-------------- tests/event_sorting.rs | 217 +------------- tests/res_with_auth_ids.rs | 567 ++++++------------------------------- tests/state_res.rs | 487 +------------------------------ tests/utils.rs | 505 +++++++++++++++++++++++++++++++++ 5 files changed, 607 insertions(+), 1403 deletions(-) create mode 100644 tests/utils.rs diff --git a/tests/event_auth.rs b/tests/event_auth.rs index fe3df512..be5689da 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -1,242 +1,16 @@ -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; +use std::sync::Arc; -use ruma::{ - events::{ - room::{ - join_rules::JoinRule, - member::{MemberEventContent, MembershipState}, - }, - EventType, - }, - identifiers::{EventId, RoomId, UserId}, -}; -use serde_json::{json, Value as JsonValue}; #[rustfmt::skip] // this deletes the comments for some reason yay! use state_res::{ event_auth::{ // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, valid_membership_change, }, - Requester, StateEvent, StateMap, StateStore, Result, Error + Requester, StateMap }; -use tracing_subscriber as tracer; -use std::sync::Once; - -static LOGGER: Once = Once::new(); - -static mut SERVER_TIMESTAMP: i32 = 0; - -fn event_id(id: &str) -> EventId { - if id.contains('$') { - return EventId::try_from(id).unwrap(); - } - EventId::try_from(format!("${}:foo", id)).unwrap() -} - -fn alice() -> UserId { - UserId::try_from("@alice:foo").unwrap() -} -fn bob() -> UserId { - UserId::try_from("@bob:foo").unwrap() -} -fn charlie() -> UserId { - UserId::try_from("@charlie:foo").unwrap() -} - -fn room_id() -> RoomId { - RoomId::try_from("!test:foo").unwrap() -} - -fn member_content_ban() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Ban, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -fn member_content_join() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Join, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -pub struct TestStore(BTreeMap>); - -#[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { - self.0 - .get(event_id) - .map(Arc::clone) - .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) - } -} - -fn to_pdu_event( - id: &str, - sender: UserId, - ev_type: EventType, - state_key: Option<&str>, - content: JsonValue, - auth_events: &[S], - prev_events: &[S], -) -> Arc -where - S: AsRef, -{ - let ts = unsafe { - let ts = SERVER_TIMESTAMP; - // increment the "origin_server_ts" value - SERVER_TIMESTAMP += 1; - ts - }; - let id = if id.contains('$') { - id.to_string() - } else { - format!("${}:foo", id) - }; - let auth_events = auth_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - // .map(|id| { - // ( - // id, - // EventHash { - // sha256: "hello".into(), - // }, - // ) - // }) - .collect::>(); - let prev_events = prev_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - // .map(|id| { - // ( - // id, - // EventHash { - // sha256: "hello".into(), - // }, - // ) - // }) - .collect::>(); - - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) -} - -// all graphs start with these input events -#[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { - // this is always called so we can init the logger here - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - vec![ - to_pdu_event::( - "CREATE", - alice(), - EventType::RoomCreate, - Some(""), - json!({ "creator": alice() }), - &[], - &[], - ), - to_pdu_event( - "IMA", - alice(), - EventType::RoomMember, - Some(alice().to_string().as_str()), - member_content_join(), - &["CREATE"], - &["CREATE"], - ), - to_pdu_event( - "IPOWER", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice().to_string(): 100}}), - &["CREATE", "IMA"], - &["IMA"], - ), - to_pdu_event( - "IJR", - alice(), - EventType::RoomJoinRules, - Some(""), - json!({ "join_rule": JoinRule::Public }), - &["CREATE", "IMA", "IPOWER"], - &["IPOWER"], - ), - to_pdu_event( - "IMB", - bob(), - EventType::RoomMember, - Some(bob().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IJR"], - ), - to_pdu_event( - "IMC", - charlie(), - EventType::RoomMember, - Some(charlie().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IMB"], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} +mod utils; +use utils::{alice, charlie, event_id, member_content_ban, room_id, INITIAL_EVENTS}; #[test] fn test_ban_pass() { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 8897a4a2..6c49a153 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -1,217 +1,13 @@ -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; +use std::collections::BTreeMap; use ruma::{ - events::{ - room::{ - join_rules::JoinRule, - member::{MemberEventContent, MembershipState}, - }, - EventType, - }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + events::EventType, + identifiers::{EventId, RoomVersionId}, }; -use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateEvent, StateMap, StateStore}; -use tracing_subscriber as tracer; +use state_res::StateMap; -use std::sync::Once; - -static LOGGER: Once = Once::new(); - -static mut SERVER_TIMESTAMP: i32 = 0; - -fn event_id(id: &str) -> EventId { - if id.contains('$') { - return EventId::try_from(id).unwrap(); - } - EventId::try_from(format!("${}:foo", id)).unwrap() -} - -fn alice() -> UserId { - UserId::try_from("@alice:foo").unwrap() -} -fn bob() -> UserId { - UserId::try_from("@bob:foo").unwrap() -} -fn charlie() -> UserId { - UserId::try_from("@charlie:foo").unwrap() -} - -fn room_id() -> RoomId { - RoomId::try_from("!test:foo").unwrap() -} - -fn member_content_join() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Join, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -pub struct TestStore(BTreeMap>); - -#[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { - self.0 - .get(event_id) - .map(Arc::clone) - .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) - } -} - -fn to_pdu_event( - id: &str, - sender: UserId, - ev_type: EventType, - state_key: Option<&str>, - content: JsonValue, - auth_events: &[S], - prev_events: &[S], -) -> Arc -where - S: AsRef, -{ - let ts = unsafe { - let ts = SERVER_TIMESTAMP; - // increment the "origin_server_ts" value - SERVER_TIMESTAMP += 1; - ts - }; - let id = if id.contains('$') { - id.to_string() - } else { - format!("${}:foo", id) - }; - let auth_events = auth_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - let prev_events = prev_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) -} - -// all graphs start with these input events -#[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { - // this is always called so we can init the logger here - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - vec![ - to_pdu_event::( - "CREATE", - alice(), - EventType::RoomCreate, - Some(""), - json!({ "creator": alice() }), - &[], - &[], - ), - to_pdu_event( - "IMA", - alice(), - EventType::RoomMember, - Some(alice().to_string().as_str()), - member_content_join(), - &["CREATE"], - &["CREATE"], - ), - to_pdu_event( - "IPOWER", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice().to_string(): 100}}), - &["CREATE", "IMA"], - &["IMA"], - ), - to_pdu_event( - "IJR", - alice(), - EventType::RoomJoinRules, - Some(""), - json!({ "join_rule": JoinRule::Public }), - &["CREATE", "IMA", "IPOWER"], - &["IPOWER"], - ), - to_pdu_event( - "IMB", - bob(), - EventType::RoomMember, - Some(bob().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IJR"], - ), - to_pdu_event( - "IMC", - charlie(), - EventType::RoomMember, - Some(charlie().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IMB"], - ), - to_pdu_event::( - "END", - charlie(), - EventType::RoomTopic, - Some(""), - json!({}), - &[], - &[], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} +mod utils; +use utils::{room_id, TestStore, INITIAL_EVENTS}; fn shuffle(list: &mut [EventId]) { use rand::Rng; @@ -286,6 +82,7 @@ fn test_event_sort() { "$IJR:foo", "$IMB:foo", "$IMC:foo", + "$START:foo", "$END:foo" ], sorted_event_ids diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index e5e22e1e..eabb201b 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -1,471 +1,19 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call)] -use std::{ - collections::BTreeMap, - convert::TryFrom, - sync::{Arc, Once}, - time::UNIX_EPOCH, -}; +use std::{collections::BTreeMap, sync::Arc}; use ruma::{ - events::{ - room::{ - join_rules::JoinRule, - member::{MemberEventContent, MembershipState}, - }, - EventType, - }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + events::EventType, + identifiers::{EventId, RoomVersionId}, }; -use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateEvent, StateMap, StateResolution, StateStore}; -use tracing_subscriber as tracer; +use serde_json::json; +use state_res::{StateEvent, StateMap, StateResolution}; -static LOGGER: Once = Once::new(); - -static mut SERVER_TIMESTAMP: i32 = 0; - -fn do_check( - events: &[Arc], - edges: Vec>, - expected_state_ids: Vec, -) { - // to activate logging use `RUST_LOG=debug cargo t` - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - let mut store = TestStore( - INITIAL_EVENTS() - .values() - .chain(events) - .map(|ev| (ev.event_id(), ev.clone())) - .collect(), - ); - - // This will be lexi_topo_sorted for resolution - let mut graph = BTreeMap::new(); - // this is the same as in `resolve` event_id -> StateEvent - let mut fake_event_map = BTreeMap::new(); - - // create the DB of events that led up to this point - // TODO maybe clean up some of these clones it is just tests but... - for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id().clone(), vec![]); - fake_event_map.insert(ev.event_id().clone(), ev.clone()); - } - - for pair in INITIAL_EDGES().windows(2) { - if let [a, b] = &pair { - graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); - } - } - - for edge_list in edges { - for pair in edge_list.windows(2) { - if let [a, b] = &pair { - graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); - } - } - } - - // event_id -> StateEvent - let mut event_map: BTreeMap> = BTreeMap::new(); - // event_id -> StateMap - let mut state_at_event: BTreeMap> = BTreeMap::new(); - - // resolve the current state and add it to the state_at_event map then continue - // on in "time" - for node in - StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) - { - let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id(); - - let prev_events = graph.get(&node).unwrap(); - - let state_before: StateMap = if prev_events.is_empty() { - BTreeMap::new() - } else if prev_events.len() == 1 { - state_at_event.get(&prev_events[0]).unwrap().clone() - } else { - let state_sets = prev_events - .iter() - .filter_map(|k| state_at_event.get(k)) - .cloned() - .collect::>(); - - tracing::info!( - "{:#?}", - state_sets - .iter() - .map(|map| map - .iter() - .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) - .collect::>()) - .collect::>() - ); - - let resolved = StateResolution::resolve( - &room_id(), - &RoomVersionId::Version1, - &state_sets, - Some(event_map.clone()), - &store, - ); - match resolved { - Ok(state) => state, - Err(e) => panic!("resolution for {} failed: {}", node, e), - } - }; - - let mut state_after = state_before.clone(); - - // if fake_event.state_key().is_some() { - let ty = fake_event.kind().clone(); - // we know there is a state_key unwrap OK - let key = fake_event.state_key().clone(); - state_after.insert((ty, key), event_id.clone()); - // } - - let auth_types = state_res::auth_types_for_event( - fake_event.kind(), - fake_event.sender(), - Some(fake_event.state_key()), - fake_event.content().clone(), - ); - - let mut auth_events = vec![]; - for key in auth_types { - if state_before.contains_key(&key) { - auth_events.push(state_before[&key].clone()) - } - } - - // TODO The event is just remade, adding the auth_events and prev_events here - // the `to_pdu_event` was split into `init` and the fn below, could be better - let e = fake_event; - let ev_id = e.event_id(); - let event = to_pdu_event( - &e.event_id().to_string(), - e.sender().clone(), - e.kind(), - Some(e.state_key()).as_deref(), - e.content().clone(), - &auth_events, - prev_events, - ); - - // we have to update our store, an actual user of this lib would - // be giving us state from a DB. - store.0.insert(ev_id.clone(), event.clone()); - - state_at_event.insert(node, state_after); - event_map.insert(event_id.clone(), Arc::clone(store.0.get(&ev_id).unwrap())); - } - - let mut expected_state = StateMap::new(); - for node in expected_state_ids { - let ev = event_map.get(&node).expect(&format!( - "{} not found in {:?}", - node.to_string(), - event_map - .keys() - .map(ToString::to_string) - .collect::>(), - )); - - let key = (ev.kind(), ev.state_key()); - - expected_state.insert(key, node); - } - - let start_state = state_at_event.get(&event_id("$START:foo")).unwrap(); - - let end_state = state_at_event - .get(&event_id("$END:foo")) - .unwrap() - .iter() - .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(); - - assert_eq!(expected_state, end_state); -} -pub struct TestStore(BTreeMap>); - -#[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { - self.0 - .get(event_id) - .map(Arc::clone) - .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) - } -} - -fn event_id(id: &str) -> EventId { - if id.contains('$') { - return EventId::try_from(id).unwrap(); - } - EventId::try_from(format!("${}:foo", id)).unwrap() -} - -fn alice() -> UserId { - UserId::try_from("@alice:foo").unwrap() -} -fn bob() -> UserId { - UserId::try_from("@bob:foo").unwrap() -} -fn charlie() -> UserId { - UserId::try_from("@charlie:foo").unwrap() -} -fn ella() -> UserId { - UserId::try_from("@ella:foo").unwrap() -} -fn zara() -> UserId { - UserId::try_from("@zara:foo").unwrap() -} - -fn room_id() -> RoomId { - RoomId::try_from("!test:foo").unwrap() -} - -fn member_content_ban() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Ban, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -fn member_content_join() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Join, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -fn to_pdu_event( - id: &str, - sender: UserId, - ev_type: EventType, - state_key: Option<&str>, - content: JsonValue, - auth_events: &[S], - prev_events: &[S], -) -> Arc -where - S: AsRef, -{ - let ts = unsafe { - let ts = SERVER_TIMESTAMP; - // increment the "origin_server_ts" value - SERVER_TIMESTAMP += 1; - ts - }; - let id = if id.contains('$') { - id.to_string() - } else { - format!("${}:foo", id) - }; - let auth_events = auth_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - let prev_events = prev_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) -} - -// all graphs start with these input events -#[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { - // this is always called so we can init the logger here - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - vec![ - to_pdu_event::( - "CREATE", - alice(), - EventType::RoomCreate, - Some(""), - json!({ "creator": alice() }), - &[], - &[], - ), - to_pdu_event( - "IMA", - alice(), - EventType::RoomMember, - Some(alice().to_string().as_str()), - member_content_join(), - &["CREATE"], - &["CREATE"], - ), - to_pdu_event( - "IPOWER", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice().to_string(): 100}}), - &["CREATE", "IMA"], - &["IMA"], - ), - to_pdu_event( - "IJR", - alice(), - EventType::RoomJoinRules, - Some(""), - json!({ "join_rule": JoinRule::Public }), - &["CREATE", "IMA", "IPOWER"], - &["IPOWER"], - ), - to_pdu_event( - "IMB", - bob(), - EventType::RoomMember, - Some(bob().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IJR"], - ), - to_pdu_event( - "IMC", - charlie(), - EventType::RoomMember, - Some(charlie().to_string().as_str()), - member_content_join(), - &["CREATE", "IJR", "IPOWER"], - &["IMB"], - ), - to_pdu_event::( - "START", - charlie(), - EventType::RoomTopic, - Some(""), - json!({}), - &[], - &[], - ), - to_pdu_event::( - "END", - charlie(), - EventType::RoomTopic, - Some(""), - json!({}), - &[], - &[], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} - -#[allow(non_snake_case)] -fn INITIAL_EDGES() -> Vec { - vec!["START", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"] - .into_iter() - .map(event_id) - .collect::>() -} - -// all graphs start with these input events -#[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { - vec![ - to_pdu_event( - "PA", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice(): 100, bob(): 50}}), - &["CREATE", "IMA", "IPOWER"], // auth_events - &["START"], // prev_events - ), - to_pdu_event( - "PB", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice(): 100, bob(): 50}}), - &["CREATE", "IMA", "IPOWER"], - &["END"], - ), - to_pdu_event( - "MB", - alice(), - EventType::RoomMember, - Some(ella().as_str()), - member_content_ban(), - &["CREATE", "IMA", "PB"], - &["PA"], - ), - to_pdu_event( - "IME", - ella(), - EventType::RoomMember, - Some(ella().as_str()), - member_content_join(), - &["CREATE", "IJR", "PA"], - &["MB"], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} +mod utils; +use utils::{ + alice, bob, do_check, ella, event_id, member_content_ban, member_content_join, room_id, + to_pdu_event, zara, TestStore, INITIAL_EVENTS, +}; #[test] fn ban_with_auth_chains() { @@ -488,12 +36,13 @@ fn ban_with_auth_chains() { ); } +// Sanity check that the store is able to fetch auth chain and such #[test] fn base_with_auth_chains() { let store = TestStore(INITIAL_EVENTS()); let resolved: BTreeMap<_, EventId> = - match StateResolution::resolve(&room_id(), &RoomVersionId::Version2, &[], None, &store) { + match StateResolution::resolve(&room_id(), &RoomVersionId::Version6, &[], None, &store) { Ok(state) => state, Err(e) => panic!("{}", e), }; @@ -558,7 +107,7 @@ fn ban_with_auth_chains2() { let resolved: StateMap = match StateResolution::resolve( &room_id(), - &RoomVersionId::Version2, + &RoomVersionId::Version6, &[state_set_a, state_set_b], None, &store, @@ -586,7 +135,7 @@ fn ban_with_auth_chains2() { ]; for id in expected.iter().map(|i| event_id(i)) { - // make sure our resolved events are equall to the expected list + // make sure our resolved events are equal to the expected list assert!( resolved.values().any(|eid| eid == &id) || init.contains_key(&id), "{}", @@ -596,7 +145,72 @@ fn ban_with_auth_chains2() { assert_eq!(expected.len(), resolved.len()) } -// all graphs start with these input events +#[test] +fn join_rule_with_auth_chain() { + let join_rule = JOIN_RULE(); + + let edges = vec![vec!["END", "JR", "START"], vec!["END", "IMZ", "START"]] + .into_iter() + .map(|list| list.into_iter().map(event_id).collect::>()) + .collect::>(); + + let expected_state_ids = vec!["JR", "END"] + .into_iter() + .map(event_id) + .collect::>(); + + do_check( + &join_rule.values().cloned().collect::>(), + edges, + expected_state_ids, + ); +} + +#[allow(non_snake_case)] +fn BAN_STATE_SET() -> BTreeMap> { + vec![ + to_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], // auth_events + &["START"], // prev_events + ), + to_pdu_event( + "PB", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], + &["END"], + ), + to_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_ban(), + &["CREATE", "IMA", "PB"], + &["PA"], + ), + to_pdu_event( + "IME", + ella(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_join(), + &["CREATE", "IJR", "PA"], + &["MB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + #[allow(non_snake_case)] fn JOIN_RULE() -> BTreeMap> { vec![ @@ -623,24 +237,3 @@ fn JOIN_RULE() -> BTreeMap> { .map(|ev| (ev.event_id(), ev)) .collect() } - -#[test] -fn join_rule_with_auth_chain() { - let join_rule = JOIN_RULE(); - - let edges = vec![vec!["END", "JR", "START"], vec!["END", "IMZ", "START"]] - .into_iter() - .map(|list| list.into_iter().map(event_id).collect::>()) - .collect::>(); - - let expected_state_ids = vec!["JR", "END"] - .into_iter() - .map(event_id) - .collect::>(); - - do_check( - &join_rule.values().cloned().collect::>(), - edges, - expected_state_ids, - ); -} diff --git a/tests/state_res.rs b/tests/state_res.rs index 00efa3a4..f7c69fe2 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -1,470 +1,19 @@ -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, time::UNIX_EPOCH}; +use std::{sync::Arc, time::UNIX_EPOCH}; use maplit::btreemap; use ruma::{ - events::{ - room::{ - join_rules::JoinRule, - member::{MemberEventContent, MembershipState}, - }, - EventType, - }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + events::{room::join_rules::JoinRule, EventType}, + identifiers::{EventId, RoomVersionId}, }; -use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateEvent, StateMap, StateResolution, StateStore}; +use serde_json::json; +use state_res::{StateMap, StateResolution}; use tracing_subscriber as tracer; -use std::sync::Once; - -static LOGGER: Once = Once::new(); - -static mut SERVER_TIMESTAMP: i32 = 0; - -fn event_id(id: &str) -> EventId { - if id.contains('$') { - return EventId::try_from(id).unwrap(); - } - EventId::try_from(format!("${}:foo", id)).unwrap() -} - -fn alice() -> UserId { - UserId::try_from("@alice:foo").unwrap() -} -fn bob() -> UserId { - UserId::try_from("@bob:foo").unwrap() -} -fn charlie() -> UserId { - UserId::try_from("@charlie:foo").unwrap() -} -fn ella() -> UserId { - UserId::try_from("@ella:foo").unwrap() -} -fn zera() -> UserId { - UserId::try_from("@zera:foo").unwrap() -} - -fn room_id() -> RoomId { - RoomId::try_from("!test:foo").unwrap() -} - -fn member_content_ban() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Ban, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} -fn member_content_join() -> JsonValue { - serde_json::to_value(MemberEventContent { - membership: MembershipState::Join, - displayname: None, - avatar_url: None, - is_direct: None, - third_party_invite: None, - }) - .unwrap() -} - -fn to_pdu_event( - id: &str, - sender: UserId, - ev_type: EventType, - state_key: Option<&str>, - content: JsonValue, - auth_events: &[S], - prev_events: &[S], -) -> Arc -where - S: AsRef, -{ - let ts = unsafe { - let ts = SERVER_TIMESTAMP; - // increment the "origin_server_ts" value - SERVER_TIMESTAMP += 1; - ts - }; - let id = if id.contains('$') { - id.to_string() - } else { - format!("${}:foo", id) - }; - let auth_events = auth_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - let prev_events = prev_events - .iter() - .map(AsRef::as_ref) - .map(event_id) - .collect::>(); - - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) -} - -fn to_init_pdu_event( - id: &str, - sender: UserId, - ev_type: EventType, - state_key: Option<&str>, - content: JsonValue, -) -> Arc { - let ts = unsafe { - let ts = SERVER_TIMESTAMP; - // increment the "origin_server_ts" value - SERVER_TIMESTAMP += 1; - ts - }; - let id = if id.contains('$') { - id.to_string() - } else { - format!("${}:foo", id) - }; - - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": [], - "prev_events": [], - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": [], - "prev_events": [], - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) -} - -// all graphs start with these input events -#[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { - vec![ - to_init_pdu_event( - "CREATE", - alice(), - EventType::RoomCreate, - Some(""), - json!({ "creator": alice() }), - ), - to_init_pdu_event( - "IMA", - alice(), - EventType::RoomMember, - Some(alice().to_string().as_str()), - member_content_join(), - ), - to_init_pdu_event( - "IPOWER", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice().to_string(): 100}}), - ), - to_init_pdu_event( - "IJR", - alice(), - EventType::RoomJoinRules, - Some(""), - json!({ "join_rule": JoinRule::Public }), - ), - to_init_pdu_event( - "IMB", - bob(), - EventType::RoomMember, - Some(bob().to_string().as_str()), - member_content_join(), - ), - to_init_pdu_event( - "IMC", - charlie(), - EventType::RoomMember, - Some(charlie().to_string().as_str()), - member_content_join(), - ), - to_init_pdu_event( - "IMZ", - zera(), - EventType::RoomMember, - Some(zera().to_string().as_str()), - member_content_join(), - ), - to_init_pdu_event("START", zera(), EventType::RoomTopic, Some(""), json!({})), - to_init_pdu_event("END", zera(), EventType::RoomTopic, Some(""), json!({})), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} - -#[allow(non_snake_case)] -fn INITIAL_EDGES() -> Vec { - vec![ - "START", "IMZ", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE", - ] - .into_iter() - .map(event_id) - .collect::>() -} - -fn do_check( - events: &[Arc], - edges: Vec>, - expected_state_ids: Vec, -) { - // to activate logging use `RUST_LOG=debug cargo t one_test_only` - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - let mut store = TestStore( - INITIAL_EVENTS() - .values() - .chain(events) - .map(|ev| (ev.event_id(), ev.clone())) - .collect(), - ); - - // This will be lexi_topo_sorted for resolution - let mut graph = BTreeMap::new(); - // this is the same as in `resolve` event_id -> StateEvent - let mut fake_event_map = BTreeMap::new(); - - // create the DB of events that led up to this point - // TODO maybe clean up some of these clones it is just tests but... - for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id(), vec![]); - fake_event_map.insert(ev.event_id(), ev.clone()); - } - - for pair in INITIAL_EDGES().windows(2) { - if let [a, b] = &pair { - graph - .entry(a.clone()) - .or_insert_with(Vec::new) - .push(b.clone()); - } - } - - for edge_list in edges { - for pair in edge_list.windows(2) { - if let [a, b] = &pair { - graph - .entry(a.clone()) - .or_insert_with(Vec::new) - .push(b.clone()); - } - } - } - - // event_id -> StateEvent - let mut event_map: BTreeMap> = BTreeMap::new(); - // event_id -> StateMap - let mut state_at_event: BTreeMap> = BTreeMap::new(); - - // resolve the current state and add it to the state_at_event map then continue - // on in "time" - for node in - StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) - { - let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id(); - - let prev_events = graph.get(&node).unwrap(); - - let state_before: StateMap = if prev_events.is_empty() { - BTreeMap::new() - } else if prev_events.len() == 1 { - state_at_event.get(&prev_events[0]).unwrap().clone() - } else { - let state_sets = prev_events - .iter() - .filter_map(|k| state_at_event.get(k)) - .cloned() - .collect::>(); - - tracing::debug!( - "{:#?}", - state_sets - .iter() - .map(|map| map - .iter() - .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) - .collect::>()) - .collect::>() - ); - - let resolved = StateResolution::resolve( - &room_id(), - &RoomVersionId::Version6, - &state_sets, - Some(event_map.clone()), - &store, - ); - match resolved { - Ok(state) => state, - Err(e) => panic!("resolution for {} failed: {}", node, e), - } - }; - - let mut state_after = state_before.clone(); - - let ty = fake_event.kind().clone(); - let key = fake_event.state_key().clone(); - state_after.insert((ty, key), event_id.clone()); - - let auth_types = state_res::auth_types_for_event( - fake_event.kind(), - fake_event.sender(), - Some(fake_event.state_key()), - fake_event.content().clone(), - ); - - let mut auth_events = vec![]; - for key in auth_types { - if state_before.contains_key(&key) { - auth_events.push(state_before[&key].clone()) - } - } - - // TODO The event is just remade, adding the auth_events and prev_events here - // UPDATE: the `to_pdu_event` was split into `init` and the fn below, could be better - let e = fake_event; - let ev_id = e.event_id(); - let event = to_pdu_event( - &e.event_id().to_string(), - e.sender().clone(), - e.kind(), - Some(e.state_key()).as_deref(), - e.content().clone(), - &auth_events, - prev_events, - ); - - // This can be used to sort of test this function - // match StateResolution::apply_event( - // &room_id(), - // &RoomVersionId::Version6, - // Arc::clone(&event), - // &state_after, - // Some(event_map.clone()), - // &store, - // ) { - // Ok(res) => { - // println!( - // "res contains: {} passed: {} for {}\n{:?}", - // state_after - // .get(&(event.kind(), event.state_key())) - // .map(|id| id == &ev_id) - // .unwrap_or_default(), - // res, - // event.event_id().as_str(), - // event - // .prev_event_ids() - // .iter() - // .map(|id| id.to_string()) - // .collect::>() - // ); - // } - // Err(e) => panic!("resolution for {} failed: {}", node, e), - // } - - // we have to update our store, an actual user of this lib would - // be giving us state from a DB. - store.0.insert(ev_id.clone(), Arc::clone(&event)); - - state_at_event.insert(node, state_after); - event_map.insert(event_id.clone(), event); - } - - let mut expected_state = StateMap::new(); - for node in expected_state_ids { - let ev = event_map.get(&node).unwrap_or_else(|| { - panic!( - "{} not found in {:?}", - node.to_string(), - event_map - .keys() - .map(ToString::to_string) - .collect::>(), - ) - }); - - let key = (ev.kind(), ev.state_key()); - - expected_state.insert(key, node); - } - - let start_state = state_at_event.get(&event_id("$START:foo")).unwrap(); - - let end_state = state_at_event - .get(&event_id("$END:foo")) - .unwrap() - .iter() - .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(); - - assert_eq!(expected_state, end_state); -} +mod utils; +use utils::{ + alice, bob, charlie, do_check, ella, event_id, member_content_ban, member_content_join, + room_id, to_init_pdu_event, to_pdu_event, zara, TestStore, LOGGER, +}; #[test] fn ban_vs_power_level() { @@ -692,7 +241,7 @@ fn topic_setting() { json!({"users": {alice(): 100, bob(): 50}}), ), to_init_pdu_event("T3", bob(), EventType::RoomTopic, Some(""), json!({})), - to_init_pdu_event("MZ1", zera(), EventType::RoomTopic, Some(""), json!({})), + to_init_pdu_event("MZ1", zara(), EventType::RoomTopic, Some(""), json!({})), to_init_pdu_event("T4", alice(), EventType::RoomTopic, Some(""), json!({})), ]; @@ -758,20 +307,6 @@ fn test_lexicographical_sort() { // A StateStore implementation for testing // // - -/// The test state store. -pub struct TestStore(BTreeMap>); - -#[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { - self.0 - .get(event_id) - .map(Arc::clone) - .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) - } -} - impl TestStore { pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { // to activate logging use `RUST_LOG=debug cargo t one_test_only` diff --git a/tests/utils.rs b/tests/utils.rs new file mode 100644 index 00000000..51548078 --- /dev/null +++ b/tests/utils.rs @@ -0,0 +1,505 @@ +#![allow(clippy::or_fun_call, clippy::expect_fun_call, dead_code)] + +use std::{ + collections::BTreeMap, + convert::TryFrom, + sync::{Arc, Once}, + time::UNIX_EPOCH, +}; + +use ruma::{ + events::{ + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +use serde_json::{json, Value as JsonValue}; +use state_res::{Error, Result, StateEvent, StateMap, StateResolution, StateStore}; +use tracing_subscriber as tracer; + +pub static LOGGER: Once = Once::new(); + +static mut SERVER_TIMESTAMP: i32 = 0; + +pub fn do_check( + events: &[Arc], + edges: Vec>, + expected_state_ids: Vec, +) { + // to activate logging use `RUST_LOG=debug cargo t` + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + let mut store = TestStore( + INITIAL_EVENTS() + .values() + .chain(events) + .map(|ev| (ev.event_id(), ev.clone())) + .collect(), + ); + + // This will be lexi_topo_sorted for resolution + let mut graph = BTreeMap::new(); + // this is the same as in `resolve` event_id -> StateEvent + let mut fake_event_map = BTreeMap::new(); + + // create the DB of events that led up to this point + // TODO maybe clean up some of these clones it is just tests but... + for ev in INITIAL_EVENTS().values().chain(events) { + graph.insert(ev.event_id().clone(), vec![]); + fake_event_map.insert(ev.event_id().clone(), ev.clone()); + } + + for pair in INITIAL_EDGES().windows(2) { + if let [a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + + for edge_list in edges { + for pair in edge_list.windows(2) { + if let [a, b] = &pair { + graph.entry(a.clone()).or_insert(vec![]).push(b.clone()); + } + } + } + + // event_id -> StateEvent + let mut event_map: BTreeMap> = BTreeMap::new(); + // event_id -> StateMap + let mut state_at_event: BTreeMap> = BTreeMap::new(); + + // resolve the current state and add it to the state_at_event map then continue + // on in "time" + for node in + StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) + { + let fake_event = fake_event_map.get(&node).unwrap(); + let event_id = fake_event.event_id(); + + let prev_events = graph.get(&node).unwrap(); + + let state_before: StateMap = if prev_events.is_empty() { + BTreeMap::new() + } else if prev_events.len() == 1 { + state_at_event.get(&prev_events[0]).unwrap().clone() + } else { + let state_sets = prev_events + .iter() + .filter_map(|k| state_at_event.get(k)) + .cloned() + .collect::>(); + + tracing::info!( + "{:#?}", + state_sets + .iter() + .map(|map| map + .iter() + .map(|((ty, key), id)| format!("(({}{:?}), {})", ty, key, id)) + .collect::>()) + .collect::>() + ); + + let resolved = StateResolution::resolve( + &room_id(), + &RoomVersionId::Version1, + &state_sets, + Some(event_map.clone()), + &store, + ); + match resolved { + Ok(state) => state, + Err(e) => panic!("resolution for {} failed: {}", node, e), + } + }; + + let mut state_after = state_before.clone(); + + // if fake_event.state_key().is_some() { + let ty = fake_event.kind().clone(); + // we know there is a state_key unwrap OK + let key = fake_event.state_key().clone(); + state_after.insert((ty, key), event_id.clone()); + // } + + let auth_types = state_res::auth_types_for_event( + fake_event.kind(), + fake_event.sender(), + Some(fake_event.state_key()), + fake_event.content().clone(), + ); + + let mut auth_events = vec![]; + for key in auth_types { + if state_before.contains_key(&key) { + auth_events.push(state_before[&key].clone()) + } + } + + // TODO The event is just remade, adding the auth_events and prev_events here + // the `to_pdu_event` was split into `init` and the fn below, could be better + let e = fake_event; + let ev_id = e.event_id(); + let event = to_pdu_event( + &e.event_id().to_string(), + e.sender().clone(), + e.kind(), + Some(e.state_key()).as_deref(), + e.content().clone(), + &auth_events, + prev_events, + ); + + // This can be used to sort of test this function + // match StateResolution::apply_event( + // &room_id(), + // &RoomVersionId::Version6, + // Arc::clone(&event), + // &state_after, + // Some(event_map.clone()), + // &store, + // ) { + // Ok(res) => { + // println!( + // "res contains: {} passed: {} for {}\n{:?}", + // state_after + // .get(&(event.kind(), event.state_key())) + // .map(|id| id == &ev_id) + // .unwrap_or_default(), + // res, + // event.event_id().as_str(), + // event + // .prev_event_ids() + // .iter() + // .map(|id| id.to_string()) + // .collect::>() + // ); + // } + // Err(e) => panic!("resolution for {} failed: {}", node, e), + // } + + // we have to update our store, an actual user of this lib would + // be giving us state from a DB. + store.0.insert(ev_id.clone(), event.clone()); + + state_at_event.insert(node, state_after); + event_map.insert(event_id.clone(), Arc::clone(store.0.get(&ev_id).unwrap())); + } + + let mut expected_state = StateMap::new(); + for node in expected_state_ids { + let ev = event_map.get(&node).expect(&format!( + "{} not found in {:?}", + node.to_string(), + event_map + .keys() + .map(ToString::to_string) + .collect::>(), + )); + + let key = (ev.kind(), ev.state_key()); + + expected_state.insert(key, node); + } + + let start_state = state_at_event.get(&event_id("$START:foo")).unwrap(); + + let end_state = state_at_event + .get(&event_id("$END:foo")) + .unwrap() + .iter() + .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>(); + + assert_eq!(expected_state, end_state); +} + +pub struct TestStore(pub BTreeMap>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { + self.0 + .get(event_id) + .map(Arc::clone) + .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) + } +} + +pub fn event_id(id: &str) -> EventId { + if id.contains('$') { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +pub fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +pub fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +pub fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} +pub fn ella() -> UserId { + UserId::try_from("@ella:foo").unwrap() +} +pub fn zara() -> UserId { + UserId::try_from("@zara:foo").unwrap() +} + +pub fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +pub fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +pub fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +pub fn to_init_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, +) -> Arc { + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": [], + "prev_events": [], + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": [], + "prev_events": [], + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + Arc::new(serde_json::from_value(json).unwrap()) +} + +pub fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> Arc +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .collect::>(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .collect::>(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + Arc::new(serde_json::from_value(json).unwrap()) +} + +// all graphs start with these input events +#[allow(non_snake_case)] +pub fn INITIAL_EVENTS() -> BTreeMap> { + // this is always called so we can init the logger here + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + to_pdu_event::( + "START", + charlie(), + EventType::RoomTopic, + Some(""), + json!({}), + &[], + &[], + ), + to_pdu_event::( + "END", + charlie(), + EventType::RoomTopic, + Some(""), + json!({}), + &[], + &[], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + +#[allow(non_snake_case)] +pub fn INITIAL_EDGES() -> Vec { + vec!["START", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"] + .into_iter() + .map(event_id) + .collect::>() +} From 611d1a3d9c84c4e3e2fdb2cdea9b51766164d766 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 14 Dec 2020 15:10:04 -0500 Subject: [PATCH 081/130] Add tests for resolve_incoming --- src/lib.rs | 6 ++ tests/incoming.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.rs | 2 +- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 tests/incoming.rs diff --git a/src/lib.rs b/src/lib.rs index c835996c..5594e7cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,12 @@ impl StateResolution { event_auth::auth_check(room_version, &ev, prev_event, auth_events, None) } + /// Resolve the conflicted events. This function is similar to + /// `StateResolution::resolve` by sorting and iteratively auth checking + /// events, but it will not separate state sets into "clean" and "conflicting". + /// + /// The `conflicted` events can be an incoming pdu and the events needed to fill + /// the gap between the incoming event and the last known parent on the receiving server. pub fn resolve_incoming( room_id: &RoomId, room_version: &RoomVersionId, diff --git a/tests/incoming.rs b/tests/incoming.rs new file mode 100644 index 00000000..5a69f752 --- /dev/null +++ b/tests/incoming.rs @@ -0,0 +1,188 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use ruma::{ + events::EventType, + identifiers::{EventId, RoomVersionId}, +}; +use serde_json::json; +use state_res::{StateEvent, StateResolution}; +use tracing_subscriber as tracer; + +mod utils; +use utils::{ + alice, bob, ella, member_content_ban, member_content_join, room_id, to_pdu_event, zara, + TestStore, INITIAL_EVENTS, LOGGER, +}; + +#[test] +fn resolve_incoming_jr() { + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + let events = INITIAL_EVENTS(); + let conflicted = JOIN_RULE(); + let store = TestStore( + events + .clone() + .into_iter() + .chain(conflicted.clone()) + .collect(), + ); + + let res = match StateResolution::resolve_incoming( + &room_id(), + &RoomVersionId::Version6, + &events + .iter() + .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) + .collect(), + conflicted + .iter() + .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) + .collect(), + None, + &store, + ) { + Ok(state) => state, + Err(e) => panic!("{:?}", e), + }; + + assert_eq!( + vec![ + "$CREATE:foo", + "$JR:foo", + "$IMA:foo", + "$IMB:foo", + "$IMC:foo", + "$IPOWER:foo", + "$START:foo" + ], + res.values().map(|id| id.to_string()).collect::>() + ) +} + +#[test] +fn resolve_incoming_ban() { + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + let events = INITIAL_EVENTS(); + let conflicted = BAN_STATE_SET(); + let store = TestStore( + events + .clone() + .into_iter() + .chain(conflicted.clone()) + .collect(), + ); + + let res = match StateResolution::resolve_incoming( + &room_id(), + &RoomVersionId::Version6, + &events + .iter() + .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) + .collect(), + conflicted + .iter() + .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) + .collect(), + None, + &store, + ) { + Ok(state) => state, + Err(e) => panic!("{:?}", e), + }; + + assert_eq!( + vec![ + "$CREATE:foo", + "$IJR:foo", + "$IMA:foo", + "$IMB:foo", + "$IMC:foo", + "$MB:foo", + "$PB:foo", + "$START:foo" + ], + res.values().map(|id| id.to_string()).collect::>() + ) +} + +#[allow(non_snake_case)] +fn JOIN_RULE() -> BTreeMap> { + vec![ + to_pdu_event( + "JR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({"join_rule": "invite"}), + &["CREATE", "IMA", "IPOWER"], + &["START"], + ), + to_pdu_event( + "IMZ", + zara(), + EventType::RoomPowerLevels, + Some(zara().as_str()), + member_content_join(), + &["CREATE", "JR", "IPOWER"], + &["START"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + +#[allow(non_snake_case)] +fn BAN_STATE_SET() -> BTreeMap> { + vec![ + to_pdu_event( + "PA", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], // auth_events + &["START"], // prev_events + ), + to_pdu_event( + "PB", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice(): 100, bob(): 50}}), + &["CREATE", "IMA", "IPOWER"], + &["END"], + ), + to_pdu_event( + "MB", + alice(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_ban(), + &["CREATE", "IMA", "PB"], + &["PA"], + ), + to_pdu_event( + "IME", + ella(), + EventType::RoomMember, + Some(ella().as_str()), + member_content_join(), + &["CREATE", "IJR", "PA"], + &["MB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} diff --git a/tests/utils.rs b/tests/utils.rs index 51548078..d8ba48cc 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -110,7 +110,7 @@ pub fn do_check( let resolved = StateResolution::resolve( &room_id(), - &RoomVersionId::Version1, + &RoomVersionId::Version6, &state_sets, Some(event_map.clone()), &store, From 282270ed4f432f4cb0e93e24ca7d59d726e3900f Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 21 Dec 2020 16:33:26 -0500 Subject: [PATCH 082/130] Start work on db less state-res --- Cargo.toml | 4 +- src/lib.rs | 139 +--------------------------------- tests/incoming.rs | 188 ---------------------------------------------- tests/utils.rs | 3 +- 4 files changed, 6 insertions(+), 328 deletions(-) delete mode 100644 tests/incoming.rs diff --git a/Cargo.toml b/Cargo.toml index 93b36356..dbba7297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,9 @@ thiserror = "1.0.22" [dependencies.ruma] git = "https://github.com/ruma/ruma" -# branch = "dev/unstable-join" +# branch = "verified-export" # path = "../__forks__/ruma/ruma" -rev = "ee814aa84934530d76f5e4b275d739805b49bdef" +rev = "45d01011554f9d07739e9a5edf5498d8ac16f273" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] diff --git a/src/lib.rs b/src/lib.rs index 5594e7cd..5296aeff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,141 +83,6 @@ impl StateResolution { event_auth::auth_check(room_version, &ev, prev_event, auth_events, None) } - /// Resolve the conflicted events. This function is similar to - /// `StateResolution::resolve` by sorting and iteratively auth checking - /// events, but it will not separate state sets into "clean" and "conflicting". - /// - /// The `conflicted` events can be an incoming pdu and the events needed to fill - /// the gap between the incoming event and the last known parent on the receiving server. - pub fn resolve_incoming( - room_id: &RoomId, - room_version: &RoomVersionId, - unconflicted: &StateMap, - conflicted: Vec<((EventType, String), EventId)>, - event_map: Option>>, - store: &dyn StateStore, - ) -> Result> { - let mut event_map = if let Some(ev_map) = event_map { - ev_map - } else { - BTreeMap::new() - }; - - let all_ids = vec![ - unconflicted.values().cloned().collect(), - // conflicted can contain duplicates no BTreeMaps - conflicted.iter().map(|(_, v)| v).cloned().collect(), - ]; - // We use the store here since it takes a Vec instead of Vec like - // `StateResolution::get_auth_chain_diff` - let mut auth_diff = store.auth_chain_diff(room_id, all_ids)?; - - tracing::debug!("auth diff size {}", auth_diff.len()); - - // add the auth_diff to conflicting now we have a full set of conflicting events - auth_diff.extend(conflicted.iter().map(|(_, v)| v).cloned()); - let mut all_conflicted = auth_diff - .into_iter() - .collect::>() - .into_iter() - .collect::>(); - - tracing::info!("full conflicted set is {} events", all_conflicted.len()); - - // gather missing events for the event_map - let events = store - .get_events( - room_id, - &all_conflicted - .iter() - // we only want the events we don't know about yet - .filter(|id| !event_map.contains_key(id)) - .cloned() - .collect::>(), - ) - .unwrap(); - - // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); - - // Don't honor events we cannot verify. - all_conflicted.retain(|id| event_map.contains_key(id)); - - // get only the control events with a state_key: "" or ban/kick event (sender != state_key) - let control_events = all_conflicted - .iter() - .filter(|id| is_power_event(id, &event_map)) - .cloned() - .collect::>(); - - // sort the control events based on power_level/clock/event_id and outgoing/incoming edges - let mut sorted_control_levels = StateResolution::reverse_topological_power_sort( - room_id, - &control_events, - &mut event_map, - store, - &all_conflicted, - ); - - // sequentially auth check each control event. - let resolved_control = StateResolution::iterative_auth_check( - room_id, - room_version, - &sorted_control_levels, - unconflicted, - &mut event_map, - store, - )?; - - // At this point the control_events have been resolved we now have to - // sort the remaining events using the mainline of the resolved power level. - sorted_control_levels.dedup(); - let deduped_power_ev = sorted_control_levels; - - // This removes the control events that passed auth and more importantly those that failed auth - let events_to_resolve = all_conflicted - .iter() - .filter(|id| !deduped_power_ev.contains(id)) - .cloned() - .collect::>(); - - // This "epochs" power level event - let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); - - tracing::debug!("PL {:?}", power_event); - - let sorted_left_events = StateResolution::mainline_sort( - room_id, - &events_to_resolve, - power_event, - &mut event_map, - store, - ); - - tracing::debug!( - "SORTED LEFT {:?}", - sorted_left_events - .iter() - .map(ToString::to_string) - .collect::>() - ); - - let resolved_state = StateResolution::iterative_auth_check( - room_id, - room_version, - &sorted_left_events, - &resolved_control, // The control events are added to the final resolved state - &mut event_map, - store, - )?; - - // Add unconflicted state to the resolved state - // We do it this way so any resolved_state would overwrite unconflicted - let mut clean = unconflicted.clone(); - clean.extend(resolved_state); - Ok(clean) - } - /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. /// @@ -240,6 +105,7 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], + // TODO: make the `Option<&mut EventMap>>` event_map: Option>>, store: &dyn StateStore, ) -> Result> { @@ -397,8 +263,7 @@ impl StateResolution { )?; // add unconflicted state to the resolved state - // TODO: - // CLEAN OVERWRITES ANY DUPLICATE KEYS FROM RESOLVED STATE + // We priorities the unconflicting state resolved_state.extend(clean); Ok(resolved_state) } diff --git a/tests/incoming.rs b/tests/incoming.rs deleted file mode 100644 index 5a69f752..00000000 --- a/tests/incoming.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::{collections::BTreeMap, sync::Arc}; - -use ruma::{ - events::EventType, - identifiers::{EventId, RoomVersionId}, -}; -use serde_json::json; -use state_res::{StateEvent, StateResolution}; -use tracing_subscriber as tracer; - -mod utils; -use utils::{ - alice, bob, ella, member_content_ban, member_content_join, room_id, to_pdu_event, zara, - TestStore, INITIAL_EVENTS, LOGGER, -}; - -#[test] -fn resolve_incoming_jr() { - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - let events = INITIAL_EVENTS(); - let conflicted = JOIN_RULE(); - let store = TestStore( - events - .clone() - .into_iter() - .chain(conflicted.clone()) - .collect(), - ); - - let res = match StateResolution::resolve_incoming( - &room_id(), - &RoomVersionId::Version6, - &events - .iter() - .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) - .collect(), - conflicted - .iter() - .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) - .collect(), - None, - &store, - ) { - Ok(state) => state, - Err(e) => panic!("{:?}", e), - }; - - assert_eq!( - vec![ - "$CREATE:foo", - "$JR:foo", - "$IMA:foo", - "$IMB:foo", - "$IMC:foo", - "$IPOWER:foo", - "$START:foo" - ], - res.values().map(|id| id.to_string()).collect::>() - ) -} - -#[test] -fn resolve_incoming_ban() { - let _ = LOGGER.call_once(|| { - tracer::fmt() - .with_env_filter(tracer::EnvFilter::from_default_env()) - .init() - }); - - let events = INITIAL_EVENTS(); - let conflicted = BAN_STATE_SET(); - let store = TestStore( - events - .clone() - .into_iter() - .chain(conflicted.clone()) - .collect(), - ); - - let res = match StateResolution::resolve_incoming( - &room_id(), - &RoomVersionId::Version6, - &events - .iter() - .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) - .collect(), - conflicted - .iter() - .map(|(_, ev)| ((ev.kind(), ev.state_key()), ev.event_id())) - .collect(), - None, - &store, - ) { - Ok(state) => state, - Err(e) => panic!("{:?}", e), - }; - - assert_eq!( - vec![ - "$CREATE:foo", - "$IJR:foo", - "$IMA:foo", - "$IMB:foo", - "$IMC:foo", - "$MB:foo", - "$PB:foo", - "$START:foo" - ], - res.values().map(|id| id.to_string()).collect::>() - ) -} - -#[allow(non_snake_case)] -fn JOIN_RULE() -> BTreeMap> { - vec![ - to_pdu_event( - "JR", - alice(), - EventType::RoomJoinRules, - Some(""), - json!({"join_rule": "invite"}), - &["CREATE", "IMA", "IPOWER"], - &["START"], - ), - to_pdu_event( - "IMZ", - zara(), - EventType::RoomPowerLevels, - Some(zara().as_str()), - member_content_join(), - &["CREATE", "JR", "IPOWER"], - &["START"], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} - -#[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { - vec![ - to_pdu_event( - "PA", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice(): 100, bob(): 50}}), - &["CREATE", "IMA", "IPOWER"], // auth_events - &["START"], // prev_events - ), - to_pdu_event( - "PB", - alice(), - EventType::RoomPowerLevels, - Some(""), - json!({"users": {alice(): 100, bob(): 50}}), - &["CREATE", "IMA", "IPOWER"], - &["END"], - ), - to_pdu_event( - "MB", - alice(), - EventType::RoomMember, - Some(ella().as_str()), - member_content_ban(), - &["CREATE", "IMA", "PB"], - &["PA"], - ), - to_pdu_event( - "IME", - ella(), - EventType::RoomMember, - Some(ella().as_str()), - member_content_join(), - &["CREATE", "IJR", "PA"], - &["MB"], - ), - ] - .into_iter() - .map(|ev| (ev.event_id(), ev)) - .collect() -} diff --git a/tests/utils.rs b/tests/utils.rs index d8ba48cc..7eef8a15 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -71,6 +71,8 @@ pub fn do_check( } } + panic!("{}", serde_json::to_string_pretty(&graph).unwrap()); + // event_id -> StateEvent let mut event_map: BTreeMap> = BTreeMap::new(); // event_id -> StateMap @@ -125,7 +127,6 @@ pub fn do_check( // if fake_event.state_key().is_some() { let ty = fake_event.kind().clone(); - // we know there is a state_key unwrap OK let key = fake_event.state_key().clone(); state_after.insert((ty, key), event_id.clone()); // } From 5299679c2193f17b756776082c5149f68d180cec Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 22 Dec 2020 14:28:48 -0500 Subject: [PATCH 083/130] Use ruma::ServerPdu instead of local type --- Cargo.toml | 6 +- src/event_auth.rs | 224 +++++++++++++++++++++++---------------------- src/lib.rs | 150 ++++++++++++++++++------------ src/state_store.rs | 13 ++- 4 files changed, 218 insertions(+), 175 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dbba7297..138d6150 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,10 @@ maplit = "1.0.2" thiserror = "1.0.22" [dependencies.ruma] -git = "https://github.com/ruma/ruma" -# branch = "verified-export" +git = "https://github.com/DevinR528/ruma" +branch = "server-pdu" # path = "../__forks__/ruma/ruma" -rev = "45d01011554f9d07739e9a5edf5498d8ac16f273" +# rev = "45d01011554f9d07739e9a5edf5498d8ac16f273" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] diff --git a/src/event_auth.rs b/src/event_auth.rs index ee57965f..bb168c93 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -3,6 +3,7 @@ use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use maplit::btreeset; use ruma::{ events::{ + pdu::ServerPdu, room::{ self, join_rules::JoinRule, @@ -14,49 +15,44 @@ use ruma::{ identifiers::{RoomVersionId, UserId}, }; -use crate::{ - state_event::{Requester, StateEvent}, - Error, Result, StateMap, -}; +use crate::{state_event::Requester, to_requester, Error, Result, StateMap}; /// For the given event `kind` what are the relevant auth events /// that are needed to authenticate this `content`. pub fn auth_types_for_event( - kind: EventType, + kind: &EventType, sender: &UserId, state_key: Option, content: serde_json::Value, -) -> Vec<(EventType, String)> { +) -> Vec<(EventType, Option)> { if kind == EventType::RoomCreate { return vec![]; } let mut auth_types = vec![ - (EventType::RoomPowerLevels, "".to_string()), - (EventType::RoomMember, sender.to_string()), - (EventType::RoomCreate, "".to_string()), + (EventType::RoomPowerLevels, Some("".to_string())), + (EventType::RoomMember, Some(sender.to_string())), + (EventType::RoomCreate, Some("".to_string())), ]; if kind == EventType::RoomMember { if let Ok(content) = serde_json::from_value::(content) { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { - let key = (EventType::RoomJoinRules, "".into()); + let key = (EventType::RoomJoinRules, Some("".into())); if !auth_types.contains(&key) { auth_types.push(key) } } // TODO what when we don't find a state_key - if let Some(state_key) = state_key { - let key = (EventType::RoomMember, state_key); - if !auth_types.contains(&key) { - auth_types.push(key) - } + let key = (EventType::RoomMember, state_key); + if !auth_types.contains(&key) { + auth_types.push(key) } if content.membership == MembershipState::Invite { if let Some(t_id) = content.third_party_invite { - let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); + let key = (EventType::RoomThirdPartyInvite, Some(t_id.signed.token)); if !auth_types.contains(&key) { auth_types.push(key) } @@ -74,12 +70,12 @@ pub fn auth_types_for_event( /// * then there are checks for specific event types pub fn auth_check( room_version: &RoomVersionId, - incoming_event: &Arc, - prev_event: Option>, - auth_events: StateMap>, - current_third_party_invite: Option>, + incoming_event: &Arc, + prev_event: Option>, + auth_events: StateMap>, + current_third_party_invite: Option>, ) -> Result { - tracing::info!("auth_check beginning for {}", incoming_event.kind()); + tracing::info!("auth_check beginning for {}", incoming_event.kind); // [synapse] check that all the events are in the same room as `incoming_event` @@ -92,17 +88,17 @@ pub fn auth_check( // Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules // // 1. If type is m.room.create: - if incoming_event.kind() == EventType::RoomCreate { + if incoming_event.kind == EventType::RoomCreate { tracing::info!("start m.room.create check"); // If it has any previous events, reject - if !incoming_event.prev_event_ids().is_empty() { + if !incoming_event.prev_events.is_empty() { tracing::warn!("the room creation event had previous events"); return Ok(false); } // If the domain of the room_id does not match the domain of the sender, reject - if incoming_event.room_id().server_name() != incoming_event.sender().server_name() { + if incoming_event.room_id.server_name() != incoming_event.sender.server_name() { tracing::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } @@ -110,7 +106,7 @@ pub fn auth_check( // If content.room_version is present and is not a recognized version, reject if serde_json::from_value::( incoming_event - .content() + .content .get("room_version") .cloned() // TODO synapse defaults to version 1 @@ -123,7 +119,7 @@ pub fn auth_check( } // If content has no creator field, reject - if incoming_event.content().get("creator").is_none() { + if incoming_event.content.get("creator").is_none() { tracing::warn!("no creator field found in room create content"); return Ok(false); } @@ -137,9 +133,9 @@ pub fn auth_check( // a. auth_events cannot have duplicate keys since it's a BTree // b. All entries are valid auth events according to spec let expected_auth = auth_types_for_event( - incoming_event.kind(), + incoming_event.kind, incoming_event.sender(), - incoming_event.state_key(), + incoming_event.state_key, incoming_event.content().clone(), ); @@ -156,7 +152,7 @@ pub fn auth_check( // 3. If event does not have m.room.create in auth_events reject if auth_events - .get(&(EventType::RoomCreate, "".into())) + .get(&(EventType::RoomCreate, Some("".into()))) .is_none() { tracing::warn!("no m.room.create event in auth chain"); @@ -167,18 +163,18 @@ pub fn auth_check( // [synapse] checks for federation here // 4. if type is m.room.aliases - if incoming_event.kind() == EventType::RoomAliases { + if incoming_event.kind == EventType::RoomAliases { tracing::info!("starting m.room.aliases check"); // [synapse] adds `&& room_version` "special case aliases auth" // [synapse] - // if event.state_key().unwrap().is_empty() { + // if event.state_key.unwrap().is_empty() { // tracing::warn!("state_key must be non-empty"); // return Ok(false); // and be non-empty state_key (point to a user_id) // } // If sender's domain doesn't matches state_key, reject - if incoming_event.state_key() != incoming_event.sender().server_name().as_str() { + if incoming_event.state_key != Some(incoming_event.sender.server_name().to_string()) { tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -187,19 +183,20 @@ pub fn auth_check( return Ok(true); } - if incoming_event.kind() == EventType::RoomMember { + if incoming_event.kind == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if incoming_event - .deserialize_content::() - .is_err() + if serde_json::from_value::( + incoming_event.content.clone(), + ) + .is_err() { tracing::warn!("no membership filed found for m.room.member event content"); return Ok(false); } if !valid_membership_change( - incoming_event.to_requester(), + to_requester(incoming_event), prev_event, current_third_party_invite, &auth_events, @@ -212,7 +209,7 @@ pub fn auth_check( } // If the sender's current membership state is not join, reject - match check_event_sender_in_room(incoming_event.sender(), &auth_events) { + match check_event_sender_in_room(&incoming_event.sender, &auth_events) { Some(true) => {} // sender in room Some(false) => { tracing::warn!("sender's membership is not join"); @@ -226,8 +223,8 @@ pub fn auth_check( // Allow if and only if sender's current power level is greater than // or equal to the invite level - if incoming_event.kind() == EventType::RoomThirdPartyInvite - && !can_send_invite(&incoming_event.to_requester(), &auth_events)? + if incoming_event.kind == EventType::RoomThirdPartyInvite + && !can_send_invite(&to_requester(incoming_event), &auth_events)? { tracing::warn!("sender's cannot send invites in this room"); return Ok(false); @@ -240,7 +237,7 @@ pub fn auth_check( return Ok(false); } - if incoming_event.kind() == EventType::RoomPowerLevels { + if incoming_event.kind == EventType::RoomPowerLevels { tracing::info!("starting m.room.power_levels check"); if let Some(required_pwr_lvl) = @@ -257,7 +254,7 @@ pub fn auth_check( tracing::info!("power levels event allowed"); } - if incoming_event.kind() == EventType::RoomRedaction + if incoming_event.kind == EventType::RoomRedaction && !check_redaction(room_version, incoming_event, &auth_events)? { return Ok(false); @@ -278,9 +275,9 @@ pub fn auth_check( /// the current State. pub fn valid_membership_change( user: Requester<'_>, - prev_event: Option>, - current_third_party_invite: Option>, - auth_events: &StateMap>, + prev_event: Option>, + current_third_party_invite: Option>, + auth_events: &StateMap>, ) -> Result { let state_key = if let Some(s) = user.state_key.as_ref() { s @@ -296,25 +293,27 @@ pub fn valid_membership_change( let target_user_id = UserId::try_from(state_key.as_str()) .map_err(|e| Error::ConversionError(format!("{}", e)))?; - let key = (EventType::RoomMember, user.sender.to_string()); + let key = (EventType::RoomMember, Some(user.sender.to_string())); let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok(pdu - .deserialize_content::()? - .membership) + Ok( + serde_json::from_value::(pdu.content.clone())? + .membership, + ) })?; - let key = (EventType::RoomMember, target_user_id.to_string()); + let key = (EventType::RoomMember, Some(target_user_id.to_string())); let current = auth_events.get(&key); let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok(pdu - .deserialize_content::()? - .membership) + Ok( + serde_json::from_value::(pdu.content.clone())? + .membership, + ) })?; - let key = (EventType::RoomPowerLevels, "".into()); + let key = (EventType::RoomPowerLevels, Some("".into())); let power_levels = auth_events.get(&key).map_or_else( || { Ok::<_, Error>(power_levels::PowerLevelsEventContent { @@ -333,8 +332,7 @@ pub fn valid_membership_change( }) }, |power_levels| { - power_levels - .deserialize_content::() + serde_json::from_value::(power_levels.content) .map_err(Into::into) }, )?; @@ -362,17 +360,17 @@ pub fn valid_membership_change( Some, ); - let key = (EventType::RoomJoinRules, "".to_string()); + let key = (EventType::RoomJoinRules, Some("".into())); let join_rules_event = auth_events.get(&key); let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { - join_rules = jr - .deserialize_content::()? - .join_rule; + join_rules = + serde_json::from_value::(jr.content.clone())? + .join_rule; } if let Some(prev) = prev_event { - if prev.kind() == EventType::RoomCreate && prev.prev_event_ids().is_empty() { + if prev.kind == EventType::RoomCreate && prev.prev_events.is_empty() { return Ok(true); } } @@ -434,11 +432,11 @@ pub fn valid_membership_change( /// Is the event's sender in the room that they sent the event to. pub fn check_event_sender_in_room( sender: &UserId, - auth_events: &StateMap>, + auth_events: &StateMap>, ) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; + let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; Some( - mem.deserialize_content::() + serde_json::from_value::(mem.content.clone()) .ok()? .membership == MembershipState::Join, @@ -447,15 +445,15 @@ pub fn check_event_sender_in_room( /// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// have the correct userId as it's state_key if it's not the "" state_key. -pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { - let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); +pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { + let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); - let event_type_power_level = get_send_level(event.kind(), Some(event.state_key()), ple); - let user_level = get_user_power_level(event.sender(), auth_events); + let event_type_power_level = get_send_level(event.kind, event.state_key, ple); + let user_level = get_user_power_level(&event.sender, auth_events); tracing::debug!( "{} ev_type {} usr {}", - event.event_id().to_string(), + event.event_id.to_string(), event_type_power_level, user_level ); @@ -464,7 +462,9 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap, auth_events: &StateMap, - auth_events: &StateMap>, + power_event: &Arc, + auth_events: &StateMap>, ) -> Option { - let key = (power_event.kind(), power_event.state_key()); + let key = (power_event.kind, power_event.state_key); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { @@ -487,17 +487,19 @@ pub fn check_power_levels( // If users key in content is not a dictionary with keys that are valid user IDs // with values that are integers (or a string that is an integer), reject. - let user_content = power_event - .deserialize_content::() - .unwrap(); - let current_content = current_state - .deserialize_content::() - .unwrap(); + let user_content = serde_json::from_value::( + power_event.content.clone(), + ) + .unwrap(); + let current_content = serde_json::from_value::( + current_state.content.clone(), + ) + .unwrap(); // validation of users is done in Ruma, synapse for loops validating user_ids and integers here tracing::info!("validation of power event finished"); - let user_level = get_user_power_level(power_event.sender(), auth_events); + let user_level = get_user_power_level(&power_event.sender, auth_events); let mut user_levels_to_check = btreeset![]; let old_list = ¤t_content.users; @@ -547,7 +549,7 @@ pub fn check_power_levels( } // If the current value is equal to the sender's current power level, reject - if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { + if user != &power_event.sender && old_level.map(|int| (*int).into()) == Some(user_level) { tracing::warn!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } @@ -620,10 +622,10 @@ fn get_deserialize_levels( /// Does the event redacting come from a user with enough power to redact the given event. pub fn check_redaction( room_version: &RoomVersionId, - redaction_event: &Arc, - auth_events: &StateMap>, + redaction_event: &Arc, + auth_events: &StateMap>, ) -> Result { - let user_level = get_user_power_level(redaction_event.sender(), auth_events); + 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 { @@ -641,8 +643,8 @@ pub fn check_redaction( // version 1 check if let RoomVersionId::Version1 = room_version { // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow - if redaction_event.event_id().server_name() - == redaction_event.redacts().and_then(|id| id.server_name()) + if redaction_event.event_id.server_name() + == redaction_event.redacts.and_then(|id| id.server_name()) { tracing::info!("redaction event allowed via room version 1 rules"); return Ok(true); @@ -655,10 +657,10 @@ pub fn check_redaction( /// Check that the member event matches `state`. /// /// This function returns false instead of failing when deserialization fails. -pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { +pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { if let Some(event) = member_event { if let Ok(content) = - serde_json::from_value::(event.content().clone()) + serde_json::from_value::(event.content.clone()) { content.membership == state } else { @@ -670,10 +672,10 @@ pub fn check_membership(member_event: Option>, state: Membership } /// Can this room federate based on its m.room.create event. -pub fn can_federate(auth_events: &StateMap>) -> bool { - let creation_event = auth_events.get(&(EventType::RoomCreate, "".into())); +pub fn can_federate(auth_events: &StateMap>) -> bool { + let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); if let Some(ev) = creation_event { - if let Some(fed) = ev.content().get("m.federate") { + if let Some(fed) = ev.content.get("m.federate") { fed == "true" } else { false @@ -685,11 +687,11 @@ pub fn can_federate(auth_events: &StateMap>) -> bool { /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. -pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { - let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, "".into())); +pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { + let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { // TODO do this the right way and deserialize - if let Some(level) = pl.content().get(name) { + if let Some(level) = pl.content.get(name) { level.to_string().parse().unwrap_or(default) } else { 0 @@ -701,10 +703,11 @@ pub fn get_named_level(auth_events: &StateMap>, name: &str, defa /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// object. -pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { - if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { - if let Ok(content) = pl.deserialize_content::() - { +pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { + if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { + if let Ok(content) = serde_json::from_value::( + pl.content.clone(), + ) { if let Some(level) = content.users.get(user_id) { (*level).into() } else { @@ -715,9 +718,11 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap() { + if let Ok(c) = + serde_json::from_value::(create.content.clone()) + { if &c.creator == user_id { 100 } else { @@ -737,12 +742,12 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap, - power_lvl: Option<&Arc>, + power_lvl: Option<&Arc>, ) -> i64 { tracing::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { if let Ok(content) = serde_json::from_value::( - ple.content().clone(), + ple.content.clone(), ) { let mut lvl: i64 = content .events @@ -767,17 +772,16 @@ pub fn get_send_level( /// Check user can send invite. pub fn can_send_invite( event: &Requester<'_>, - auth_events: &StateMap>, + auth_events: &StateMap>, ) -> Result { let user_level = get_user_power_level(event.sender, auth_events); - let key = (EventType::RoomPowerLevels, "".into()); + let key = (EventType::RoomPowerLevels, Some("".into())); let invite_level = auth_events .get(&key) .map_or_else( || Ok::<_, Error>(ruma::int!(50)), |power_levels| { - power_levels - .deserialize_content::() + serde_json::from_value::(power_levels.content.clone()) .map(|pl| pl.invite) .map_err(Into::into) }, @@ -790,7 +794,7 @@ pub fn can_send_invite( pub fn verify_third_party_invite( event: &Requester<'_>, tp_id: &member::ThirdPartyInvite, - current_third_party_invite: Option>, + current_third_party_invite: Option>, ) -> bool { // 1. check for user being banned happens before this is called // checking for mxid and token keys is done by ruma when deserializing @@ -802,18 +806,18 @@ pub fn verify_third_party_invite( // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key() != tp_id.signed.token { + if current_tpid.state_key != Some(tp_id.signed.token) { return false; } - if event.sender != current_tpid.sender() { + if event.sender != ¤t_tpid.sender { return false; } // If any signature in signed matches any public key in the m.room.third_party_invite event, allow if let Ok(tpid_ev) = serde_json::from_value::< ruma::events::room::third_party_invite::ThirdPartyInviteEventContent, - >(current_tpid.content().clone()) + >(current_tpid.content.clone()) { // A list of public keys in the public_keys field for key in tpid_ev.public_keys.unwrap_or_default() { diff --git a/src/lib.rs b/src/lib.rs index 5296aeff..214e3077 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use maplit::btreeset; use ruma::{ - events::EventType, + events::{pdu::ServerPdu, EventType}, identifiers::{EventId, RoomId, RoomVersionId}, }; @@ -19,7 +19,7 @@ mod state_store; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; -pub use state_event::{Requester, StateEvent}; +pub use state_event::Requester; pub use state_store::StateStore; // We want to yield to the reactor occasionally during state res when dealing @@ -28,9 +28,9 @@ pub use state_store::StateStore; const _YIELD_AFTER_ITERATIONS: usize = 100; /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, String), T>; +pub type StateMap = BTreeMap<(EventType, Option), T>; -/// A mapping of `EventId` to `T`, usually a `StateEvent`. +/// A mapping of `EventId` to `T`, usually a `ServerPdu`. pub type EventMap = BTreeMap; #[derive(Default)] @@ -44,9 +44,9 @@ impl StateResolution { pub fn apply_event( room_id: &RoomId, room_version: &RoomVersionId, - incoming_event: Arc, + incoming_event: Arc, current_state: &StateMap, - event_map: Option>>, + event_map: Option>>, store: &dyn StateStore, ) -> Result { tracing::info!("Applying a single event, state resolution starting"); @@ -57,19 +57,16 @@ impl StateResolution { } else { EventMap::new() }; - let prev_event = if let Some(id) = ev.prev_event_ids().first() { + let prev_event = if let Some(id) = ev.prev_events.first() { store.get_event(room_id, id).ok() } else { None }; let mut auth_events = StateMap::new(); - for key in event_auth::auth_types_for_event( - ev.kind(), - ev.sender(), - Some(ev.state_key()), - ev.content().clone(), - ) { + for key in + event_auth::auth_types_for_event(ev.kind, &ev.sender, ev.state_key, ev.content.clone()) + { if let Some(ev_id) = current_state.get(&key) { if let Some(event) = StateResolution::get_or_load_event(room_id, ev_id, &mut event_map, store) @@ -105,8 +102,8 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], - // TODO: make the `Option<&mut EventMap>>` - event_map: Option>>, + // TODO: make the `Option<&mut EventMap>>` + event_map: Option>>, store: &dyn StateStore, ) -> Result> { tracing::info!("State resolution starting"); @@ -157,7 +154,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); + event_map.extend(events.into_iter().map(|ev| (ev.event_id.clone(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -233,7 +230,7 @@ impl StateResolution { ); // This "epochs" power level event - let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); + let power_event = resolved_control.get(&(EventType::RoomPowerLevels, Some("".into()))); tracing::debug!("PL {:?}", power_event); @@ -341,7 +338,7 @@ impl StateResolution { pub fn reverse_topological_power_sort( room_id: &RoomId, events_to_sort: &[EventId], - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, auth_diff: &[EventId], ) -> Vec { @@ -381,12 +378,12 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!("{:?}", (-*pl, *ev.origin_server_ts(), ev.event_id())); + tracing::debug!("{:?}", (-*pl, ev.origin_server_ts, ev.event_id)); // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, *ev.origin_server_ts(), ev.event_id()) + (-*pl, ev.origin_server_ts, ev.event_id) }) } @@ -467,7 +464,7 @@ impl StateResolution { fn get_power_level_for_sender( room_id: &RoomId, event_id: &EventId, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event ({}) senders power level", event_id.to_string()); @@ -479,11 +476,11 @@ impl StateResolution { // event.auth_event_ids does not include its own event id ? for aid in event .as_ref() - .map(|pdu| pdu.auth_events()) + .map(|pdu| pdu.auth_events) .unwrap_or_default() { if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { - if aev.is_type_and_key(EventType::RoomPowerLevels, "") { + if is_type_and_key(&aev, EventType::RoomPowerLevels, "") { pl = Some(aev); break; } @@ -496,15 +493,16 @@ impl StateResolution { if let Some(content) = pl .map(|pl| { - pl.deserialize_content::( + serde_json::from_value::( + pl.content.clone(), ) .ok() }) .flatten() { if let Some(ev) = event { - if let Some(user) = content.users.get(ev.sender()) { - tracing::debug!("found {} at power_level {}", ev.sender().to_string(), user); + if let Some(user) = content.users.get(&ev.sender) { + tracing::debug!("found {} at power_level {}", ev.sender.to_string(), user); return (*user).into(); } } @@ -529,7 +527,7 @@ impl StateResolution { room_version: &RoomVersionId, events_to_check: &[EventId], unconflicted_state: &StateMap, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -549,23 +547,23 @@ impl StateResolution { StateResolution::get_or_load_event(room_id, event_id, event_map, store).unwrap(); let mut auth_events = BTreeMap::new(); - for aid in event.auth_events() { + for aid in &event.auth_events { if let Some(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? - auth_events.insert((ev.kind(), ev.state_key()), ev); + auth_events.insert((ev.kind.clone(), ev.state_key.clone()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); } } for key in event_auth::auth_types_for_event( - event.kind(), - event.sender(), - Some(event.state_key()), - event.content().clone(), + event.kind, + &event.sender, + event.state_key, + event.content.clone(), ) { if let Some(ev_id) = resolved_state.get(&key) { if let Some(event) = @@ -577,10 +575,10 @@ impl StateResolution { } } - tracing::debug!("event to check {:?}", event.event_id().as_str()); + tracing::debug!("event to check {:?}", event.event_id.as_str()); let most_recent_prev_event = event - .prev_event_ids() + .prev_events .iter() .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) .next_back(); @@ -588,7 +586,7 @@ impl StateResolution { // The key for this is (eventType + a state_key of the signed token not sender) so search // for it let current_third_party = auth_events.iter().find_map(|(_, pdu)| { - if pdu.kind() == EventType::RoomThirdPartyInvite { + if pdu.kind == EventType::RoomThirdPartyInvite { Some(pdu.clone()) // TODO no clone, auth_events is borrowed while moved } else { None @@ -603,7 +601,10 @@ impl StateResolution { current_third_party, )? { // add event to resolved state map - resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); + resolved_state.insert( + (event.kind.clone(), event.state_key.clone()), + event_id.clone(), + ); } else { // synapse passes here on AuthError. We do not add this event to resolved_state. tracing::warn!( @@ -632,7 +633,7 @@ impl StateResolution { room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of events"); @@ -649,12 +650,12 @@ impl StateResolution { mainline.push(p.clone()); let event = StateResolution::get_or_load_event(room_id, &p, event_map, store).unwrap(); - let auth_events = event.auth_events(); + let auth_events = &event.auth_events; pl = None; for aid in auth_events { let ev = StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); - if ev.is_type_and_key(EventType::RoomPowerLevels, "") { + if is_type_and_key(&ev, EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; } @@ -690,10 +691,7 @@ impl StateResolution { ev_id, ( depth, - event_map - .get(ev_id) - .map(|ev| ev.origin_server_ts()) - .cloned(), + event_map.get(ev_id).map(|ev| ev.origin_server_ts), ev_id, // TODO should this be a &str to sort lexically?? ), ); @@ -719,26 +717,26 @@ impl StateResolution { /// that has an associated mainline depth. fn get_mainline_depth( room_id: &RoomId, - mut event: Option>, + mut event: Option>, mainline_map: &EventMap, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Result { while let Some(sort_ev) = event { - tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); - let id = sort_ev.event_id(); + tracing::debug!("mainline event_id {}", sort_ev.event_id.to_string()); + let id = &sort_ev.event_id; if let Some(depth) = mainline_map.get(&id) { return Ok(*depth); } // dbg!(&sort_ev); - let auth_events = sort_ev.auth_events(); + let auth_events = &sort_ev.auth_events; event = None; for aid in auth_events { // dbg!(&aid); let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store) .ok_or_else(|| Error::NotFound("Auth event not found".to_owned()))?; - if aev.is_type_and_key(EventType::RoomPowerLevels, "") { + if is_type_and_key(&aev, EventType::RoomPowerLevels, "") { event = Some(aev); break; } @@ -752,7 +750,7 @@ impl StateResolution { room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, auth_diff: &[EventId], ) { @@ -763,9 +761,9 @@ impl StateResolution { graph.entry(eid.clone()).or_insert_with(Vec::new); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in StateResolution::get_or_load_event(room_id, &eid, event_map, store) + for aid in &StateResolution::get_or_load_event(room_id, &eid, event_map, store) .unwrap() - .auth_events() + .auth_events { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { @@ -788,9 +786,9 @@ impl StateResolution { fn get_or_load_event( room_id: &RoomId, ev_id: &EventId, - event_map: &mut EventMap>, + event_map: &mut EventMap>, store: &dyn StateStore, - ) -> Option> { + ) -> Option> { if let Some(e) = event_map.get(ev_id) { return Some(Arc::clone(e)); } @@ -803,9 +801,47 @@ impl StateResolution { } } -pub fn is_power_event(event_id: &EventId, event_map: &EventMap>) -> bool { +pub fn is_power_event(event_id: &EventId, event_map: &EventMap>) -> bool { match event_map.get(event_id) { - Some(state) => state.is_power_event(), + Some(state) => _is_power_event(state), _ => false, } } + +pub fn is_type_and_key(&ev: &Arc, ev_type: EventType, state_key: &str) -> bool { + ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) +} + +fn _is_power_event(&event: &Arc) -> bool { + use ruma::events::room::member::{MemberEventContent, MembershipState}; + match event.kind { + EventType::RoomPowerLevels | EventType::RoomJoinRules | EventType::RoomCreate => { + event.state_key == Some("".into()) + } + EventType::RoomMember => { + if let Ok(content) = + // TODO fix clone + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban].contains(&content.membership) { + return event.sender.as_str() + // TODO is None here a failure + != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); + } + } + + false + } + _ => false, + } +} + +pub fn to_requester(event: &Arc) -> Requester<'_> { + Requester { + prev_event_ids: event.prev_events, + room_id: &event.room_id, + content: &event.content, + state_key: event.state_key.clone(), + sender: &event.sender, + } +} diff --git a/src/state_store.rs b/src/state_store.rs index 2f18692e..796d06fa 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,15 +1,18 @@ use std::{collections::BTreeSet, sync::Arc}; -use ruma::identifiers::{EventId, RoomId}; +use ruma::{ + events::pdu::ServerPdu, + identifiers::{EventId, RoomId}, +}; -use crate::{Result, StateEvent}; +use crate::Result; pub trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { + fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { let mut events = vec![]; for id in event_ids { events.push(self.get_event(room_id, id)?); @@ -33,7 +36,7 @@ pub trait StateStore { let event = self.get_event(room_id, &ev_id)?; - stack.extend(event.auth_events()); + stack.extend(event.auth_events.clone()); } Ok(result) From 05a4dd1bf0518f4b14192f038fb8b1038271cf14 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 28 Dec 2020 17:46:41 -0500 Subject: [PATCH 084/130] Convert state-res to use possible ruma::ServerPdu --- Cargo.toml | 10 ----- benches/state_res_bench.rs | 53 +++++++++++++------------- src/event_auth.rs | 24 +++++++----- src/lib.rs | 31 ++++++++------- tests/event_auth.rs | 8 ++-- tests/event_sorting.rs | 10 ++--- tests/res_with_auth_ids.rs | 25 ++++++------- tests/state_res.rs | 46 +++++++++++------------ tests/utils.rs | 77 +++++++++++++++++++------------------- 9 files changed, 141 insertions(+), 143 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 138d6150..08874472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,16 +26,6 @@ branch = "server-pdu" # rev = "45d01011554f9d07739e9a5edf5498d8ac16f273" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] -#[dependencies.ruma] -#path = "../ruma/ruma" -#features = ["client-api", "federation-api", "appservice-api"] - -# [dependencies.ruma] -# git = "https://github.com/timokoesters/ruma" -# branch = "timo-fed-fixes" -# #rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" -# features = ["client-api", "federation-api", "appservice-api"] - [features] default = ["unstable-pre-spec"] gen-eventid = [] diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index ed2f8d47..fd85513c 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -10,6 +10,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; use ruma::{ events::{ + pdu::ServerPdu, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -19,7 +20,7 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateEvent, StateMap, StateResolution, StateStore}; +use state_res::{Error, Result, StateMap, StateResolution, StateStore}; static mut SERVER_TIMESTAMP: i32 = 0; @@ -81,7 +82,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) .collect::>(); let state_set_b = [ @@ -94,7 +95,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) .collect::>(); b.iter(|| { @@ -114,7 +115,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { criterion_group!( benches, - lexico_topo_sort, + // lexico_topo_sort, resolution_shallow_auth_chain, resolve_deeper_event_set ); @@ -126,11 +127,11 @@ criterion_main!(benches); // IMPLEMENTATION DETAILS AHEAD // /////////////////////////////////////////////////////////////////////*/ -pub struct TestStore(BTreeMap>); +pub struct TestStore(BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) @@ -149,7 +150,7 @@ impl TestStore { &[], &[], )); - let cre = create_event.event_id(); + let cre = create_event.event_id.clone(); self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( @@ -161,7 +162,8 @@ impl TestStore { &[cre.clone()], &[cre.clone()], ); - self.0.insert(alice_mem.event_id(), Arc::clone(&alice_mem)); + self.0 + .insert(alice_mem.event_id.clone(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -169,11 +171,11 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id()], - &[alice_mem.event_id()], + &[cre.clone(), alice_mem.event_id.clone()], + &[alice_mem.event_id.clone()], ); self.0 - .insert(join_rules.event_id(), Arc::clone(&join_rules)); + .insert(join_rules.event_id.clone(), Arc::clone(&join_rules)); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -183,10 +185,11 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id()], - &[join_rules.event_id()], + &[cre.clone(), join_rules.event_id.clone()], + &[join_rules.event_id.clone()], ); - self.0.insert(bob_mem.event_id(), Arc::clone(&bob_mem)); + self.0 + .insert(bob_mem.event_id.clone(), Arc::clone(&bob_mem)); let charlie_mem = to_pdu_event( "IMC", @@ -194,20 +197,20 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id()], - &[join_rules.event_id()], + &[cre, join_rules.event_id.clone()], + &[join_rules.event_id.clone()], ); self.0 - .insert(charlie_mem.event_id(), Arc::clone(&charlie_mem)); + .insert(charlie_mem.event_id.clone(), Arc::clone(&charlie_mem)); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); let expected = [ @@ -218,7 +221,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); (state_at_bob, state_at_charlie, expected) @@ -279,7 +282,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> Arc +) -> Arc where S: AsRef, { @@ -342,7 +345,7 @@ where // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { +fn INITIAL_EVENTS() -> BTreeMap> { vec![ to_pdu_event::( "CREATE", @@ -418,13 +421,13 @@ fn INITIAL_EVENTS() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id(), ev)) + .map(|ev| (ev.event_id.clone(), ev)) .collect() } // all graphs start with these input events #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -464,6 +467,6 @@ fn BAN_STATE_SET() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id(), ev)) + .map(|ev| (ev.event_id.clone(), ev)) .collect() } diff --git a/src/event_auth.rs b/src/event_auth.rs index bb168c93..05159aea 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -25,7 +25,7 @@ pub fn auth_types_for_event( state_key: Option, content: serde_json::Value, ) -> Vec<(EventType, Option)> { - if kind == EventType::RoomCreate { + if kind == &EventType::RoomCreate { return vec![]; } @@ -35,7 +35,7 @@ pub fn auth_types_for_event( (EventType::RoomCreate, Some("".to_string())), ]; - if kind == EventType::RoomMember { + if kind == &EventType::RoomMember { if let Ok(content) = serde_json::from_value::(content) { if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { let key = (EventType::RoomJoinRules, Some("".into())); @@ -332,7 +332,7 @@ pub fn valid_membership_change( }) }, |power_levels| { - serde_json::from_value::(power_levels.content) + serde_json::from_value::(power_levels.content.clone()) .map_err(Into::into) }, )?; @@ -448,7 +448,7 @@ pub fn check_event_sender_in_room( pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); - let event_type_power_level = get_send_level(event.kind, event.state_key, ple); + let event_type_power_level = get_send_level(&event.kind, event.state_key.clone(), ple); let user_level = get_user_power_level(&event.sender, auth_events); tracing::debug!( @@ -462,7 +462,10 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap, auth_events: &StateMap>, ) -> Option { - let key = (power_event.kind, power_event.state_key); + let key = (power_event.kind.clone(), power_event.state_key.clone()); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { @@ -644,7 +647,10 @@ pub fn check_redaction( if let RoomVersionId::Version1 = room_version { // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow if redaction_event.event_id.server_name() - == redaction_event.redacts.and_then(|id| id.server_name()) + == redaction_event + .redacts + .as_ref() + .and_then(|id| id.server_name()) { tracing::info!("redaction event allowed via room version 1 rules"); return Ok(true); @@ -740,7 +746,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap, power_lvl: Option<&Arc>, ) -> i64 { @@ -806,7 +812,7 @@ pub fn verify_third_party_invite( // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key != Some(tp_id.signed.token) { + if current_tpid.state_key.as_ref() != Some(&tp_id.signed.token) { return false; } diff --git a/src/lib.rs b/src/lib.rs index 214e3077..01a7113a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,9 +64,12 @@ impl StateResolution { }; let mut auth_events = StateMap::new(); - for key in - event_auth::auth_types_for_event(ev.kind, &ev.sender, ev.state_key, ev.content.clone()) - { + for key in event_auth::auth_types_for_event( + &ev.kind, + &ev.sender, + ev.state_key.clone(), + ev.content.clone(), + ) { if let Some(ev_id) = current_state.get(&key) { if let Some(event) = StateResolution::get_or_load_event(room_id, ev_id, &mut event_map, store) @@ -170,7 +173,7 @@ impl StateResolution { // get only the control events with a state_key: "" or ban/kick event (sender != state_key) let control_events = all_conflicted .iter() - .filter(|id| is_power_event(id, &event_map)) + .filter(|id| is_power_event_id(id, &event_map)) .cloned() .collect::>(); @@ -378,12 +381,12 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!("{:?}", (-*pl, ev.origin_server_ts, ev.event_id)); + tracing::debug!("{:?}", (-*pl, ev.origin_server_ts, &ev.event_id)); // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, ev.origin_server_ts, ev.event_id) + (-*pl, ev.origin_server_ts, ev.event_id.clone()) }) } @@ -476,7 +479,7 @@ impl StateResolution { // event.auth_event_ids does not include its own event id ? for aid in event .as_ref() - .map(|pdu| pdu.auth_events) + .map(|pdu| pdu.auth_events.to_vec()) .unwrap_or_default() { if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { @@ -560,9 +563,9 @@ impl StateResolution { } for key in event_auth::auth_types_for_event( - event.kind, + &event.kind, &event.sender, - event.state_key, + event.state_key.clone(), event.content.clone(), ) { if let Some(ev_id) = resolved_state.get(&key) { @@ -801,18 +804,18 @@ impl StateResolution { } } -pub fn is_power_event(event_id: &EventId, event_map: &EventMap>) -> bool { +pub fn is_power_event_id(event_id: &EventId, event_map: &EventMap>) -> bool { match event_map.get(event_id) { - Some(state) => _is_power_event(state), + Some(state) => is_power_event(state), _ => false, } } -pub fn is_type_and_key(&ev: &Arc, ev_type: EventType, state_key: &str) -> bool { +pub fn is_type_and_key(ev: &Arc, ev_type: EventType, state_key: &str) -> bool { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } -fn _is_power_event(&event: &Arc) -> bool { +pub fn is_power_event(event: &Arc) -> bool { use ruma::events::room::member::{MemberEventContent, MembershipState}; match event.kind { EventType::RoomPowerLevels | EventType::RoomJoinRules | EventType::RoomCreate => { @@ -838,7 +841,7 @@ fn _is_power_event(&event: &Arc) -> bool { pub fn to_requester(event: &Arc) -> Requester<'_> { Requester { - prev_event_ids: event.prev_events, + prev_event_ids: event.prev_events.to_vec(), room_id: &event.room_id, content: &event.content, state_key: event.state_key.clone(), diff --git a/tests/event_auth.rs b/tests/event_auth.rs index be5689da..7fa976ad 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -18,12 +18,12 @@ fn test_ban_pass() { let prev = events .values() - .find(|ev| ev.event_id().as_str().contains("IMC")) + .find(|ev| ev.event_id.as_str().contains("IMC")) .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), Arc::clone(ev))) .collect::>(); let requester = Requester { @@ -43,12 +43,12 @@ fn test_ban_fail() { let prev = events .values() - .find(|ev| ev.event_id().as_str().contains("IMC")) + .find(|ev| ev.event_id.as_str().contains("IMC")) .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), Arc::clone(ev))) .collect::>(); let requester = Requester { diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 6c49a153..02d74b3a 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -4,7 +4,7 @@ use ruma::{ events::EventType, identifiers::{EventId, RoomVersionId}, }; -use state_res::StateMap; +use state_res::{is_power_event, StateMap}; mod utils; use utils::{room_id, TestStore, INITIAL_EVENTS}; @@ -25,15 +25,15 @@ fn test_event_sort() { let event_map = events .values() - .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.clone())) .collect::>(); let auth_chain = &[] as &[_]; let power_events = event_map .values() - .filter(|pdu| pdu.is_power_event()) - .map(|pdu| pdu.event_id()) + .filter(|pdu| is_power_event(&pdu)) + .map(|pdu| pdu.event_id.clone()) .collect::>(); // This is a TODO in conduit @@ -64,7 +64,7 @@ fn test_event_sort() { shuffle(&mut events_to_sort); - let power_level = resolved_power.get(&(EventType::RoomPowerLevels, "".into())); + let power_level = resolved_power.get(&(EventType::RoomPowerLevels, Some("".to_string()))); let sorted_event_ids = state_res::StateResolution::mainline_sort( &room_id(), diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index eabb201b..68a72837 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -3,11 +3,11 @@ use std::{collections::BTreeMap, sync::Arc}; use ruma::{ - events::EventType, + events::{pdu::ServerPdu, EventType}, identifiers::{EventId, RoomVersionId}, }; use serde_json::json; -use state_res::{StateEvent, StateMap, StateResolution}; +use state_res::{StateMap, StateResolution}; mod utils; use utils::{ @@ -24,7 +24,7 @@ fn ban_with_auth_chains() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA", "MB", "END"] + let expected_state_ids = vec!["PA", "MB"] .into_iter() .map(event_id) .collect::>(); @@ -50,7 +50,7 @@ fn base_with_auth_chains() { let resolved = resolved .values() .cloned() - .chain(INITIAL_EVENTS().values().map(|e| e.event_id())) + .chain(INITIAL_EVENTS().values().map(|e| e.event_id.clone())) .collect::>(); let expected = vec![ @@ -89,7 +89,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) .collect::>(); let state_set_b = [ @@ -102,7 +102,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id())) + .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) .collect::>(); let resolved: StateMap = match StateResolution::resolve( @@ -154,10 +154,7 @@ fn join_rule_with_auth_chain() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["JR", "END"] - .into_iter() - .map(event_id) - .collect::>(); + let expected_state_ids = vec!["JR"].into_iter().map(event_id).collect::>(); do_check( &join_rule.values().cloned().collect::>(), @@ -167,7 +164,7 @@ fn join_rule_with_auth_chain() { } #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -207,12 +204,12 @@ fn BAN_STATE_SET() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id(), ev)) + .map(|ev| (ev.event_id.clone(), ev)) .collect() } #[allow(non_snake_case)] -fn JOIN_RULE() -> BTreeMap> { +fn JOIN_RULE() -> BTreeMap> { vec![ to_pdu_event( "JR", @@ -234,6 +231,6 @@ fn JOIN_RULE() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id(), ev)) + .map(|ev| (ev.event_id.clone(), ev)) .collect() } diff --git a/tests/state_res.rs b/tests/state_res.rs index f7c69fe2..4017ee0d 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -56,7 +56,7 @@ fn ban_vs_power_level() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA", "MA", "MB", "END"] + let expected_state_ids = vec!["PA", "MA", "MB"] .into_iter() .map(event_id) .collect::>(); @@ -101,7 +101,7 @@ fn topic_basic() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PA2", "T2", "END"] + let expected_state_ids = vec!["PA2", "T2"] .into_iter() .map(event_id) .collect::>(); @@ -138,7 +138,7 @@ fn topic_reset() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["T1", "MB", "PA", "END"] + let expected_state_ids = vec!["T1", "MB", "PA"] .into_iter() .map(event_id) .collect::>(); @@ -170,7 +170,7 @@ fn join_rule_evasion() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec![event_id("JR"), event_id("END")]; + let expected_state_ids = vec![event_id("JR")]; do_check(events, edges, expected_state_ids) } @@ -206,10 +206,7 @@ fn offtopic_power_level() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["PC", "END"] - .into_iter() - .map(event_id) - .collect::>(); + let expected_state_ids = vec!["PC"].into_iter().map(event_id).collect::>(); do_check(events, edges, expected_state_ids) } @@ -253,7 +250,7 @@ fn topic_setting() { .map(|list| list.into_iter().map(event_id).collect::>()) .collect::>(); - let expected_state_ids = vec!["T4", "PA2", "END"] + let expected_state_ids = vec!["T4", "PA2"] .into_iter() .map(event_id) .collect::>(); @@ -324,7 +321,7 @@ impl TestStore { &[], &[], ); - let cre = create_event.event_id(); + let cre = create_event.event_id.clone(); self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( @@ -336,7 +333,8 @@ impl TestStore { &[cre.clone()], &[cre.clone()], ); - self.0.insert(alice_mem.event_id(), Arc::clone(&alice_mem)); + self.0 + .insert(alice_mem.event_id.clone(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -344,10 +342,11 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id()], - &[alice_mem.event_id()], + &[cre.clone(), alice_mem.event_id.clone()], + &[alice_mem.event_id.clone()], ); - self.0.insert(join_rules.event_id(), join_rules.clone()); + self.0 + .insert(join_rules.event_id.clone(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -357,10 +356,10 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id()], - &[join_rules.event_id()], + &[cre.clone(), join_rules.event_id.clone()], + &[join_rules.event_id.clone()], ); - self.0.insert(bob_mem.event_id(), bob_mem.clone()); + self.0.insert(bob_mem.event_id.clone(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -368,19 +367,20 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id()], - &[join_rules.event_id()], + &[cre, join_rules.event_id.clone()], + &[join_rules.event_id.clone()], ); - self.0.insert(charlie_mem.event_id(), charlie_mem.clone()); + self.0 + .insert(charlie_mem.event_id.clone(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); let expected = [ @@ -391,7 +391,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id())) + .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) .collect::>(); (state_at_bob, state_at_charlie, expected) diff --git a/tests/utils.rs b/tests/utils.rs index 7eef8a15..3195f70f 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -9,6 +9,7 @@ use std::{ use ruma::{ events::{ + pdu::ServerPdu, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -18,7 +19,7 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateEvent, StateMap, StateResolution, StateStore}; +use state_res::{Error, Result, StateMap, StateResolution, StateStore}; use tracing_subscriber as tracer; pub static LOGGER: Once = Once::new(); @@ -26,7 +27,7 @@ pub static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; pub fn do_check( - events: &[Arc], + events: &[Arc], edges: Vec>, expected_state_ids: Vec, ) { @@ -41,20 +42,20 @@ pub fn do_check( INITIAL_EVENTS() .values() .chain(events) - .map(|ev| (ev.event_id(), ev.clone())) + .map(|ev| (ev.event_id.clone(), ev.clone())) .collect(), ); // This will be lexi_topo_sorted for resolution let mut graph = BTreeMap::new(); - // this is the same as in `resolve` event_id -> StateEvent + // this is the same as in `resolve` event_id -> ServerPdu let mut fake_event_map = BTreeMap::new(); // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id().clone(), vec![]); - fake_event_map.insert(ev.event_id().clone(), ev.clone()); + graph.insert(ev.event_id.clone(), vec![]); + fake_event_map.insert(ev.event_id.clone(), ev.clone()); } for pair in INITIAL_EDGES().windows(2) { @@ -71,10 +72,8 @@ pub fn do_check( } } - panic!("{}", serde_json::to_string_pretty(&graph).unwrap()); - - // event_id -> StateEvent - let mut event_map: BTreeMap> = BTreeMap::new(); + // event_id -> ServerPdu + let mut event_map: BTreeMap> = BTreeMap::new(); // event_id -> StateMap let mut state_at_event: BTreeMap> = BTreeMap::new(); @@ -84,7 +83,7 @@ pub fn do_check( StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id(); + let event_id = fake_event.event_id.clone(); let prev_events = graph.get(&node).unwrap(); @@ -125,17 +124,17 @@ pub fn do_check( let mut state_after = state_before.clone(); - // if fake_event.state_key().is_some() { - let ty = fake_event.kind().clone(); - let key = fake_event.state_key().clone(); - state_after.insert((ty, key), event_id.clone()); - // } + if fake_event.state_key.is_some() { + let ty = fake_event.kind.clone(); + let key = fake_event.state_key.clone(); + state_after.insert((ty, key), event_id.clone()); + } let auth_types = state_res::auth_types_for_event( - fake_event.kind(), - fake_event.sender(), - Some(fake_event.state_key()), - fake_event.content().clone(), + &fake_event.kind, + &fake_event.sender, + fake_event.state_key.clone(), + fake_event.content.clone(), ); let mut auth_events = vec![]; @@ -148,13 +147,13 @@ pub fn do_check( // TODO The event is just remade, adding the auth_events and prev_events here // the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; - let ev_id = e.event_id(); + let ev_id = e.event_id.clone(); let event = to_pdu_event( - &e.event_id().to_string(), - e.sender().clone(), - e.kind(), - Some(e.state_key()).as_deref(), - e.content().clone(), + &e.event_id.clone().to_string(), + e.sender.clone(), + e.kind.clone(), + e.state_key.as_deref(), + e.content.clone(), &auth_events, prev_events, ); @@ -172,11 +171,11 @@ pub fn do_check( // println!( // "res contains: {} passed: {} for {}\n{:?}", // state_after - // .get(&(event.kind(), event.state_key())) + // .get(&(event.kind, event.state_key())) // .map(|id| id == &ev_id) // .unwrap_or_default(), // res, - // event.event_id().as_str(), + // event.event_id.clone().as_str(), // event // .prev_event_ids() // .iter() @@ -206,7 +205,7 @@ pub fn do_check( .collect::>(), )); - let key = (ev.kind(), ev.state_key()); + let key = (ev.kind.clone(), ev.state_key.clone()); expected_state.insert(key, node); } @@ -224,11 +223,11 @@ pub fn do_check( assert_eq!(expected_state, end_state); } -pub struct TestStore(pub BTreeMap>); +pub struct TestStore(pub BTreeMap>); #[allow(unused)] impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) @@ -291,7 +290,7 @@ pub fn to_init_pdu_event( ev_type: EventType, state_key: Option<&str>, content: JsonValue, -) -> Arc { +) -> Arc { let ts = unsafe { let ts = SERVER_TIMESTAMP; // increment the "origin_server_ts" value @@ -347,7 +346,7 @@ pub fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> Arc +) -> Arc where S: AsRef, { @@ -410,7 +409,7 @@ where // all graphs start with these input events #[allow(non_snake_case)] -pub fn INITIAL_EVENTS() -> BTreeMap> { +pub fn INITIAL_EVENTS() -> BTreeMap> { // this is always called so we can init the logger here let _ = LOGGER.call_once(|| { tracer::fmt() @@ -476,8 +475,8 @@ pub fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "START", charlie(), - EventType::RoomTopic, - Some(""), + EventType::RoomMessage, + None, json!({}), &[], &[], @@ -485,15 +484,15 @@ pub fn INITIAL_EVENTS() -> BTreeMap> { to_pdu_event::( "END", charlie(), - EventType::RoomTopic, - Some(""), + EventType::RoomMessage, + None, json!({}), &[], &[], ), ] .into_iter() - .map(|ev| (ev.event_id(), ev)) + .map(|ev| (ev.event_id.clone(), ev)) .collect() } From 9721042198fb5f2c4b7eb66ec6ad3ee6d3921e17 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 30 Dec 2020 15:13:07 -0500 Subject: [PATCH 085/130] Turn pdu into trait to avoid having our own PDU type --- src/event_auth.rs | 176 ++++++++++++++++++++++----------------------- src/lib.rs | 154 ++++++++++++++++++--------------------- src/state_event.rs | 45 +++++++++++- src/state_store.rs | 10 +-- 4 files changed, 203 insertions(+), 182 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 05159aea..271a1fce 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -15,7 +15,7 @@ use ruma::{ identifiers::{RoomVersionId, UserId}, }; -use crate::{state_event::Requester, to_requester, Error, Result, StateMap}; +use crate::{Error, Event, Result, StateMap}; /// For the given event `kind` what are the relevant auth events /// that are needed to authenticate this `content`. @@ -68,14 +68,14 @@ pub fn auth_types_for_event( /// * check that the event is being authenticated for the correct room /// * check that the events signatures are valid /// * then there are checks for specific event types -pub fn auth_check( +pub fn auth_check( room_version: &RoomVersionId, - incoming_event: &Arc, - prev_event: Option>, - auth_events: StateMap>, - current_third_party_invite: Option>, + incoming_event: &Arc, + prev_event: Option>, + auth_events: StateMap>, + current_third_party_invite: Option>, ) -> Result { - tracing::info!("auth_check beginning for {}", incoming_event.kind); + tracing::info!("auth_check beginning for {}", incoming_event.kind()); // [synapse] check that all the events are in the same room as `incoming_event` @@ -88,17 +88,17 @@ pub fn auth_check( // Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules // // 1. If type is m.room.create: - if incoming_event.kind == EventType::RoomCreate { + if incoming_event.kind() == EventType::RoomCreate { tracing::info!("start m.room.create check"); // If it has any previous events, reject - if !incoming_event.prev_events.is_empty() { + if !incoming_event.prev_events().is_empty() { tracing::warn!("the room creation event had previous events"); return Ok(false); } // If the domain of the room_id does not match the domain of the sender, reject - if incoming_event.room_id.server_name() != incoming_event.sender.server_name() { + if incoming_event.room_id().server_name() != incoming_event.sender().server_name() { tracing::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } @@ -106,7 +106,7 @@ pub fn auth_check( // If content.room_version is present and is not a recognized version, reject if serde_json::from_value::( incoming_event - .content + .content() .get("room_version") .cloned() // TODO synapse defaults to version 1 @@ -119,7 +119,7 @@ pub fn auth_check( } // If content has no creator field, reject - if incoming_event.content.get("creator").is_none() { + if incoming_event.content().get("creator").is_none() { tracing::warn!("no creator field found in room create content"); return Ok(false); } @@ -163,7 +163,7 @@ pub fn auth_check( // [synapse] checks for federation here // 4. if type is m.room.aliases - if incoming_event.kind == EventType::RoomAliases { + if incoming_event.kind() == EventType::RoomAliases { tracing::info!("starting m.room.aliases check"); // [synapse] adds `&& room_version` "special case aliases auth" @@ -174,7 +174,7 @@ pub fn auth_check( // } // If sender's domain doesn't matches state_key, reject - if incoming_event.state_key != Some(incoming_event.sender.server_name().to_string()) { + if incoming_event.state_key() != Some(incoming_event.sender().server_name().to_string()) { tracing::warn!("state_key does not match sender"); return Ok(false); } @@ -183,20 +183,18 @@ pub fn auth_check( return Ok(true); } - if incoming_event.kind == EventType::RoomMember { + if incoming_event.kind() == EventType::RoomMember { tracing::info!("starting m.room.member check"); - if serde_json::from_value::( - incoming_event.content.clone(), - ) - .is_err() + if serde_json::from_value::(incoming_event.content()) + .is_err() { tracing::warn!("no membership filed found for m.room.member event content"); return Ok(false); } if !valid_membership_change( - to_requester(incoming_event), + incoming_event, prev_event, current_third_party_invite, &auth_events, @@ -209,7 +207,7 @@ pub fn auth_check( } // If the sender's current membership state is not join, reject - match check_event_sender_in_room(&incoming_event.sender, &auth_events) { + match check_event_sender_in_room(&incoming_event.sender(), &auth_events) { Some(true) => {} // sender in room Some(false) => { tracing::warn!("sender's membership is not join"); @@ -223,8 +221,8 @@ pub fn auth_check( // Allow if and only if sender's current power level is greater than // or equal to the invite level - if incoming_event.kind == EventType::RoomThirdPartyInvite - && !can_send_invite(&to_requester(incoming_event), &auth_events)? + if incoming_event.kind() == EventType::RoomThirdPartyInvite + && !can_send_invite(incoming_event, &auth_events)? { tracing::warn!("sender's cannot send invites in this room"); return Ok(false); @@ -237,7 +235,7 @@ pub fn auth_check( return Ok(false); } - if incoming_event.kind == EventType::RoomPowerLevels { + if incoming_event.kind() == EventType::RoomPowerLevels { tracing::info!("starting m.room.power_levels check"); if let Some(required_pwr_lvl) = @@ -254,7 +252,7 @@ pub fn auth_check( tracing::info!("power levels event allowed"); } - if incoming_event.kind == EventType::RoomRedaction + if incoming_event.kind() == EventType::RoomRedaction && !check_redaction(room_version, incoming_event, &auth_events)? { return Ok(false); @@ -273,32 +271,31 @@ pub fn auth_check( /// * `auth_events` - The set of auth events that relate to a membership event. /// this is generated by calling `auth_types_for_event` with the membership event and /// the current State. -pub fn valid_membership_change( - user: Requester<'_>, - prev_event: Option>, - current_third_party_invite: Option>, - auth_events: &StateMap>, +pub fn valid_membership_change( + user: &Arc, + prev_event: Option>, + current_third_party_invite: Option>, + auth_events: &StateMap>, ) -> Result { - let state_key = if let Some(s) = user.state_key.as_ref() { + let state_key = if let Some(s) = user.state_key() { s } else { return Err(Error::InvalidPdu("State event requires state_key".into())); }; - let content = - serde_json::from_str::(&user.content.to_string())?; + let content = serde_json::from_value::(user.content())?; let target_membership = content.membership; let target_user_id = UserId::try_from(state_key.as_str()) .map_err(|e| Error::ConversionError(format!("{}", e)))?; - let key = (EventType::RoomMember, Some(user.sender.to_string())); + let key = (EventType::RoomMember, Some(user.sender().to_string())); let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { Ok( - serde_json::from_value::(pdu.content.clone())? + serde_json::from_value::(pdu.content())? .membership, ) })?; @@ -308,7 +305,7 @@ pub fn valid_membership_change( let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { Ok( - serde_json::from_value::(pdu.content.clone())? + serde_json::from_value::(pdu.content())? .membership, ) })?; @@ -332,12 +329,12 @@ pub fn valid_membership_change( }) }, |power_levels| { - serde_json::from_value::(power_levels.content.clone()) + serde_json::from_value::(power_levels.content()) .map_err(Into::into) }, )?; - let sender_power = power_levels.users.get(&user.sender).map_or_else( + let sender_power = power_levels.users.get(&user.sender()).map_or_else( || { if sender_membership != member::MembershipState::Join { None @@ -365,18 +362,18 @@ pub fn valid_membership_change( let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { join_rules = - serde_json::from_value::(jr.content.clone())? + serde_json::from_value::(jr.content())? .join_rule; } if let Some(prev) = prev_event { - if prev.kind == EventType::RoomCreate && prev.prev_events.is_empty() { + if prev.kind() == EventType::RoomCreate && prev.prev_events().is_empty() { return Ok(true); } } Ok(if target_membership == MembershipState::Join { - if user.sender != &target_user_id { + if user.sender() != target_user_id { false } else if let MembershipState::Ban = current_membership { false @@ -392,7 +389,7 @@ pub fn valid_membership_change( if current_membership == MembershipState::Ban { false } else { - verify_third_party_invite(&user, &tp_id, current_third_party_invite) + verify_third_party_invite(user, &tp_id, current_third_party_invite) } } else if sender_membership != MembershipState::Join || current_membership == MembershipState::Join @@ -405,7 +402,7 @@ pub fn valid_membership_change( .is_some() } } else if target_membership == MembershipState::Leave { - if user.sender == &target_user_id { + if user.sender() == target_user_id { current_membership == MembershipState::Join || current_membership == MembershipState::Invite } else if sender_membership != MembershipState::Join @@ -430,13 +427,13 @@ pub fn valid_membership_change( } /// Is the event's sender in the room that they sent the event to. -pub fn check_event_sender_in_room( +pub fn check_event_sender_in_room( sender: &UserId, - auth_events: &StateMap>, + auth_events: &StateMap>, ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; Some( - serde_json::from_value::(mem.content.clone()) + serde_json::from_value::(mem.content()) .ok()? .membership == MembershipState::Join, @@ -445,15 +442,15 @@ pub fn check_event_sender_in_room( /// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// have the correct userId as it's state_key if it's not the "" state_key. -pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { +pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); - let event_type_power_level = get_send_level(&event.kind, event.state_key.clone(), ple); - let user_level = get_user_power_level(&event.sender, auth_events); + let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple); + let user_level = get_user_power_level(&event.sender(), auth_events); tracing::debug!( "{} ev_type {} usr {}", - event.event_id.to_string(), + event.event_id().to_string(), event_type_power_level, user_level ); @@ -463,10 +460,10 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap, auth_events: &StateMap( _: &RoomVersionId, - power_event: &Arc, - auth_events: &StateMap>, + power_event: &Arc, + auth_events: &StateMap>, ) -> Option { - let key = (power_event.kind.clone(), power_event.state_key.clone()); + let key = (power_event.kind(), power_event.state_key()); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { @@ -491,18 +488,18 @@ pub fn check_power_levels( // If users key in content is not a dictionary with keys that are valid user IDs // with values that are integers (or a string that is an integer), reject. let user_content = serde_json::from_value::( - power_event.content.clone(), + power_event.content(), ) .unwrap(); let current_content = serde_json::from_value::( - current_state.content.clone(), + current_state.content(), ) .unwrap(); // validation of users is done in Ruma, synapse for loops validating user_ids and integers here tracing::info!("validation of power event finished"); - let user_level = get_user_power_level(&power_event.sender, auth_events); + let user_level = get_user_power_level(&power_event.sender(), auth_events); let mut user_levels_to_check = btreeset![]; let old_list = ¤t_content.users; @@ -552,7 +549,7 @@ pub fn check_power_levels( } // If the current value is equal to the sender's current power level, reject - if user != &power_event.sender && old_level.map(|int| (*int).into()) == Some(user_level) { + if user != &power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { tracing::warn!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } @@ -623,12 +620,12 @@ fn get_deserialize_levels( } /// Does the event redacting come from a user with enough power to redact the given event. -pub fn check_redaction( +pub fn check_redaction( room_version: &RoomVersionId, - redaction_event: &Arc, - auth_events: &StateMap>, + redaction_event: &Arc, + auth_events: &StateMap>, ) -> Result { - let user_level = get_user_power_level(&redaction_event.sender, auth_events); + 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 { @@ -646,9 +643,9 @@ pub fn check_redaction( // version 1 check if let RoomVersionId::Version1 = room_version { // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow - if redaction_event.event_id.server_name() + if redaction_event.event_id().server_name() == redaction_event - .redacts + .redacts() .as_ref() .and_then(|id| id.server_name()) { @@ -693,11 +690,11 @@ pub fn can_federate(auth_events: &StateMap>) -> bool { /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. -pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { +pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); if let Some(pl) = power_level_event { // TODO do this the right way and deserialize - if let Some(level) = pl.content.get(name) { + if let Some(level) = pl.content().get(name) { level.to_string().parse().unwrap_or(default) } else { 0 @@ -709,11 +706,11 @@ pub fn get_named_level(auth_events: &StateMap>, name: &str, defau /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// object. -pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { +pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { - if let Ok(content) = serde_json::from_value::( - pl.content.clone(), - ) { + if let Ok(content) = + serde_json::from_value::(pl.content()) + { if let Some(level) = content.users.get(user_id) { (*level).into() } else { @@ -727,7 +724,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap(create.content.clone()) + serde_json::from_value::(create.content()) { if &c.creator == user_id { 100 @@ -745,16 +742,16 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap( e_type: &EventType, state_key: Option, - power_lvl: Option<&Arc>, + power_lvl: Option<&Arc>, ) -> i64 { tracing::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { - if let Ok(content) = serde_json::from_value::( - ple.content.clone(), - ) { + if let Ok(content) = + serde_json::from_value::(ple.content()) + { let mut lvl: i64 = content .events .get(&e_type) @@ -776,18 +773,15 @@ pub fn get_send_level( } /// Check user can send invite. -pub fn can_send_invite( - event: &Requester<'_>, - auth_events: &StateMap>, -) -> Result { - let user_level = get_user_power_level(event.sender, auth_events); +pub fn can_send_invite(event: &Arc, auth_events: &StateMap>) -> Result { + let user_level = get_user_power_level(&event.sender(), auth_events); let key = (EventType::RoomPowerLevels, Some("".into())); let invite_level = auth_events .get(&key) .map_or_else( || Ok::<_, Error>(ruma::int!(50)), |power_levels| { - serde_json::from_value::(power_levels.content.clone()) + serde_json::from_value::(power_levels.content()) .map(|pl| pl.invite) .map_err(Into::into) }, @@ -797,33 +791,33 @@ pub fn can_send_invite( Ok(user_level >= invite_level) } -pub fn verify_third_party_invite( - event: &Requester<'_>, +pub fn verify_third_party_invite( + event: &Arc, tp_id: &member::ThirdPartyInvite, - current_third_party_invite: Option>, + current_third_party_invite: Option>, ) -> bool { // 1. check for user being banned happens before this is called // checking for mxid and token keys is done by ruma when deserializing - if event.state_key != Some(tp_id.signed.mxid.to_string()) { + if event.state_key() != Some(tp_id.signed.mxid.to_string()) { return false; } // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key.as_ref() != Some(&tp_id.signed.token) { + if current_tpid.state_key().as_ref() != Some(&tp_id.signed.token) { return false; } - if event.sender != ¤t_tpid.sender { + if event.sender() != current_tpid.sender() { return false; } // If any signature in signed matches any public key in the m.room.third_party_invite event, allow if let Ok(tpid_ev) = serde_json::from_value::< ruma::events::room::third_party_invite::ThirdPartyInviteEventContent, - >(current_tpid.content.clone()) + >(current_tpid.content()) { // A list of public keys in the public_keys field for key in tpid_ev.public_keys.unwrap_or_default() { diff --git a/src/lib.rs b/src/lib.rs index 01a7113a..3c923d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ mod state_store; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; -pub use state_event::Requester; +pub use state_event::{Event, Requester}; pub use state_store::StateStore; // We want to yield to the reactor occasionally during state res when dealing @@ -41,13 +41,13 @@ impl StateResolution { /// /// This will authenticate the event against the current state of the room. It /// is important that the `current_state` argument is accurate and complete. - pub fn apply_event( + pub fn apply_event( room_id: &RoomId, room_version: &RoomVersionId, - incoming_event: Arc, + incoming_event: Arc, current_state: &StateMap, - event_map: Option>>, - store: &dyn StateStore, + event_map: Option>>, + store: &dyn StateStore, ) -> Result { tracing::info!("Applying a single event, state resolution starting"); let ev = incoming_event; @@ -57,19 +57,16 @@ impl StateResolution { } else { EventMap::new() }; - let prev_event = if let Some(id) = ev.prev_events.first() { + let prev_event = if let Some(id) = ev.prev_events().first() { store.get_event(room_id, id).ok() } else { None }; let mut auth_events = StateMap::new(); - for key in event_auth::auth_types_for_event( - &ev.kind, - &ev.sender, - ev.state_key.clone(), - ev.content.clone(), - ) { + for key in + event_auth::auth_types_for_event(&ev.kind(), &ev.sender(), ev.state_key(), ev.content()) + { if let Some(ev_id) = current_state.get(&key) { if let Some(event) = StateResolution::get_or_load_event(room_id, ev_id, &mut event_map, store) @@ -101,13 +98,13 @@ impl StateResolution { /// /// It is up the the caller to check that the events returned from `StateStore::get_event` are /// events for the correct room (synapse checks that all events are in the right room). - pub fn resolve( + pub fn resolve( room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], // TODO: make the `Option<&mut EventMap>>` - event_map: Option>>, - store: &dyn StateStore, + event_map: Option>>, + store: &dyn StateStore, ) -> Result> { tracing::info!("State resolution starting"); @@ -157,7 +154,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id.clone(), ev))); + event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -312,10 +309,10 @@ impl StateResolution { } /// Returns a Vec of deduped EventIds that appear in some chains but not others. - pub fn get_auth_chain_diff( + pub fn get_auth_chain_diff( room_id: &RoomId, state_sets: &[StateMap], - store: &dyn StateStore, + store: &dyn StateStore, ) -> Result> { use itertools::Itertools; @@ -338,11 +335,11 @@ impl StateResolution { /// /// The power level is negative because a higher power level is equated to an /// earlier (further back in time) origin server timestamp. - pub fn reverse_topological_power_sort( + pub fn reverse_topological_power_sort( room_id: &RoomId, events_to_sort: &[EventId], - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, auth_diff: &[EventId], ) -> Vec { tracing::debug!("reverse topological sort of power events"); @@ -381,12 +378,12 @@ impl StateResolution { let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!("{:?}", (-*pl, ev.origin_server_ts, &ev.event_id)); + tracing::debug!("{:?}", (-*pl, ev.origin_server_ts(), &ev.event_id())); // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, ev.origin_server_ts, ev.event_id.clone()) + (-*pl, ev.origin_server_ts(), ev.event_id()) }) } @@ -464,11 +461,11 @@ impl StateResolution { } /// Find the power level for the sender of `event_id` or return a default value of zero. - fn get_power_level_for_sender( + fn get_power_level_for_sender( room_id: &RoomId, event_id: &EventId, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event ({}) senders power level", event_id.to_string()); @@ -479,7 +476,7 @@ impl StateResolution { // event.auth_event_ids does not include its own event id ? for aid in event .as_ref() - .map(|pdu| pdu.auth_events.to_vec()) + .map(|pdu| pdu.auth_events()) .unwrap_or_default() { if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { @@ -497,15 +494,15 @@ impl StateResolution { if let Some(content) = pl .map(|pl| { serde_json::from_value::( - pl.content.clone(), + pl.content(), ) .ok() }) .flatten() { if let Some(ev) = event { - if let Some(user) = content.users.get(&ev.sender) { - tracing::debug!("found {} at power_level {}", ev.sender.to_string(), user); + if let Some(user) = content.users.get(&ev.sender()) { + tracing::debug!("found {} at power_level {}", ev.sender().to_string(), user); return (*user).into(); } } @@ -525,13 +522,13 @@ impl StateResolution { /// For each `events_to_check` event we gather the events needed to auth it from the /// `event_map` or `store` and verify each event using the `event_auth::auth_check` /// function. - pub fn iterative_auth_check( + pub fn iterative_auth_check( room_id: &RoomId, room_version: &RoomVersionId, events_to_check: &[EventId], unconflicted_state: &StateMap, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -550,23 +547,23 @@ impl StateResolution { StateResolution::get_or_load_event(room_id, event_id, event_map, store).unwrap(); let mut auth_events = BTreeMap::new(); - for aid in &event.auth_events { + for aid in &event.auth_events() { if let Some(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? - auth_events.insert((ev.kind.clone(), ev.state_key.clone()), ev); + auth_events.insert((ev.kind(), ev.state_key()), ev); } else { tracing::warn!("auth event id for {} is missing {}", aid, event_id); } } for key in event_auth::auth_types_for_event( - &event.kind, - &event.sender, - event.state_key.clone(), - event.content.clone(), + &event.kind(), + &event.sender(), + event.state_key(), + event.content(), ) { if let Some(ev_id) = resolved_state.get(&key) { if let Some(event) = @@ -578,10 +575,10 @@ impl StateResolution { } } - tracing::debug!("event to check {:?}", event.event_id.as_str()); + tracing::debug!("event to check {:?}", event.event_id().as_str()); let most_recent_prev_event = event - .prev_events + .prev_events() .iter() .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) .next_back(); @@ -589,7 +586,7 @@ impl StateResolution { // The key for this is (eventType + a state_key of the signed token not sender) so search // for it let current_third_party = auth_events.iter().find_map(|(_, pdu)| { - if pdu.kind == EventType::RoomThirdPartyInvite { + if pdu.kind() == EventType::RoomThirdPartyInvite { Some(pdu.clone()) // TODO no clone, auth_events is borrowed while moved } else { None @@ -604,10 +601,7 @@ impl StateResolution { current_third_party, )? { // add event to resolved state map - resolved_state.insert( - (event.kind.clone(), event.state_key.clone()), - event_id.clone(), - ); + resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); } else { // synapse passes here on AuthError. We do not add this event to resolved_state. tracing::warn!( @@ -632,12 +626,12 @@ impl StateResolution { /// power_level event. If there have been two power events the after the most recent are /// depth 0, the events before (with the first power level as a parent) will be marked /// as depth 1. depth 1 is "older" than depth 0. - pub fn mainline_sort( + pub fn mainline_sort( room_id: &RoomId, to_sort: &[EventId], resolved_power_level: Option<&EventId>, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of events"); @@ -653,7 +647,7 @@ impl StateResolution { mainline.push(p.clone()); let event = StateResolution::get_or_load_event(room_id, &p, event_map, store).unwrap(); - let auth_events = &event.auth_events; + let auth_events = &event.auth_events(); pl = None; for aid in auth_events { let ev = @@ -694,7 +688,7 @@ impl StateResolution { ev_id, ( depth, - event_map.get(ev_id).map(|ev| ev.origin_server_ts), + event_map.get(ev_id).map(|ev| ev.origin_server_ts()), ev_id, // TODO should this be a &str to sort lexically?? ), ); @@ -718,22 +712,22 @@ impl StateResolution { /// Get the mainline depth from the `mainline_map` or finds a power_level event /// that has an associated mainline depth. - fn get_mainline_depth( + fn get_mainline_depth( room_id: &RoomId, - mut event: Option>, + mut event: Option>, mainline_map: &EventMap, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, ) -> Result { while let Some(sort_ev) = event { - tracing::debug!("mainline event_id {}", sort_ev.event_id.to_string()); - let id = &sort_ev.event_id; + tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); + let id = &sort_ev.event_id(); if let Some(depth) = mainline_map.get(&id) { return Ok(*depth); } // dbg!(&sort_ev); - let auth_events = &sort_ev.auth_events; + let auth_events = &sort_ev.auth_events(); event = None; for aid in auth_events { // dbg!(&aid); @@ -749,12 +743,12 @@ impl StateResolution { Ok(0) } - fn add_event_and_auth_chain_to_graph( + fn add_event_and_auth_chain_to_graph( room_id: &RoomId, graph: &mut BTreeMap>, event_id: &EventId, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &mut EventMap>, + store: &dyn StateStore, auth_diff: &[EventId], ) { let mut state = vec![event_id.clone()]; @@ -766,7 +760,7 @@ impl StateResolution { // otherwise it seems we can loop forever for aid in &StateResolution::get_or_load_event(room_id, &eid, event_map, store) .unwrap() - .auth_events + .auth_events() { if auth_diff.contains(&aid) { if !graph.contains_key(&aid) { @@ -786,12 +780,12 @@ impl StateResolution { /// if the event_map does not have the PDU. /// /// If the PDU is missing from the `event_map` it is added. - fn get_or_load_event( + fn get_or_load_event( room_id: &RoomId, ev_id: &EventId, - event_map: &mut EventMap>, - store: &dyn StateStore, - ) -> Option> { + event_map: &mut EventMap>, + store: &dyn StateStore, + ) -> Option> { if let Some(e) = event_map.get(ev_id) { return Some(Arc::clone(e)); } @@ -804,32 +798,32 @@ impl StateResolution { } } -pub fn is_power_event_id(event_id: &EventId, event_map: &EventMap>) -> bool { +pub fn is_power_event_id(event_id: &EventId, event_map: &EventMap>) -> bool { match event_map.get(event_id) { Some(state) => is_power_event(state), _ => false, } } -pub fn is_type_and_key(ev: &Arc, ev_type: EventType, state_key: &str) -> bool { - ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) +pub fn is_type_and_key(ev: &Arc, ev_type: EventType, state_key: &str) -> bool { + ev.kind() == ev_type && ev.state_key().as_deref() == Some(state_key) } -pub fn is_power_event(event: &Arc) -> bool { +pub fn is_power_event(event: &Arc) -> bool { use ruma::events::room::member::{MemberEventContent, MembershipState}; - match event.kind { + match event.kind() { EventType::RoomPowerLevels | EventType::RoomJoinRules | EventType::RoomCreate => { - event.state_key == Some("".into()) + event.state_key() == Some("".into()) } EventType::RoomMember => { if let Ok(content) = // TODO fix clone - serde_json::from_value::(event.content.clone()) + serde_json::from_value::(event.content()) { if [MembershipState::Leave, MembershipState::Ban].contains(&content.membership) { - return event.sender.as_str() + return event.sender().as_str() // TODO is None here a failure - != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); + != event.state_key().as_deref().unwrap_or("NOT A STATE KEY"); } } @@ -838,13 +832,3 @@ pub fn is_power_event(event: &Arc) -> bool { _ => false, } } - -pub fn to_requester(event: &Arc) -> Requester<'_> { - Requester { - prev_event_ids: event.prev_events.to_vec(), - room_id: &event.room_id, - content: &event.content, - state_key: event.state_key.clone(), - sender: &event.sender, - } -} diff --git a/src/state_event.rs b/src/state_event.rs index 141b896a..ab87b56f 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -8,12 +8,55 @@ use ruma::{ EventDeHelper, EventType, }, serde::CanonicalJsonValue, - signatures::reference_hash, + signatures::{reference_hash, CanonicalJsonObject}, EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, }; use serde::{de, ser, Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; +/// Abstraction of a PDU so users can have their own PDU types. +pub trait Event { + /// The `EventId` of this event. + fn event_id(&self) -> EventId; + + /// The `RoomId` of this event. + fn room_id(&self) -> RoomId; + + /// The `UserId` of this event. + fn sender(&self) -> UserId; + + /// The time of creation on the originating server. + fn origin_server_ts(&self) -> SystemTime; + + /// The kind of event. + fn kind(&self) -> EventType; + + /// The `UserId` of this PDU. + fn content(&self) -> serde_json::Value; + + /// The state key for this event. + fn state_key(&self) -> Option; + + /// The events before this event. + fn prev_events(&self) -> Vec; + + /// The maximum number of `prev_events` plus 1. + fn depth(&self) -> UInt; + + /// All the authenticating events for this event. + fn auth_events(&self) -> Vec; + + /// If this event is a redaction event this is the event it redacts. + fn redacts(&self) -> Option; + + /// The `unsigned` content of this event. + fn unsigned(&self) -> CanonicalJsonObject; + + fn hashes(&self) -> &EventHash; + + fn signatures(&self) -> BTreeMap, BTreeMap>; +} + #[derive(Clone, Debug, Deserialize, Serialize)] struct EventIdHelper { event_id: EventId, diff --git a/src/state_store.rs b/src/state_store.rs index 796d06fa..9f863fca 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -5,14 +5,14 @@ use ruma::{ identifiers::{EventId, RoomId}, }; -use crate::Result; +use crate::{Event, Result}; -pub trait StateStore { +pub trait StateStore { /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { + fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { let mut events = vec![]; for id in event_ids { events.push(self.get_event(room_id, id)?); @@ -36,7 +36,7 @@ pub trait StateStore { let event = self.get_event(room_id, &ev_id)?; - stack.extend(event.auth_events.clone()); + stack.extend(event.auth_events().clone()); } Ok(result) From da14be30009052dfaa15dad5a2151d089fd34217 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 30 Dec 2020 16:27:28 -0500 Subject: [PATCH 086/130] Fix tests to work with PDU event trait --- benches/outcomes.txt | 24 +- benches/state_res_bench.rs | 454 ++++++++++++++++++++++++++++++--- src/event_auth.rs | 6 +- src/lib.rs | 6 +- src/state_event.rs | 411 +----------------------------- src/state_store.rs | 5 +- tests/event_auth.rs | 48 ++-- tests/event_sorting.rs | 6 +- tests/res_with_auth_ids.rs | 18 +- tests/state_res.rs | 34 +-- tests/utils.rs | 497 ++++++++++++++++++++++++++++++++++--- 11 files changed, 984 insertions(+), 525 deletions(-) diff --git a/benches/outcomes.txt b/benches/outcomes.txt index cde1855e..2de257ec 100644 --- a/benches/outcomes.txt +++ b/benches/outcomes.txt @@ -14,4 +14,26 @@ Found 3 outliers among 100 measurements (3.00%) 3 (3.00%) high mild resolve state of 10 events 3 conflicting - time: [26.858 us 26.946 us 27.037 us] \ No newline at end of file + time: [26.858 us 26.946 us 27.037 us] + +11/29/2020 BRANCH: event-trait REV: f0eb1310efd49d722979f57f20bd1ac3592b0479 +lexicographical topological sort + time: [1.7686 us 1.7738 us 1.7810 us] + change: [-3.2752% -2.4634% -1.7635%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high severe + +resolve state of 5 events one fork + time: [10.643 us 10.656 us 10.669 us] + change: [-4.9990% -3.8078% -2.8319%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high severe + +resolve state of 10 events 3 conflicting + time: [29.149 us 29.252 us 29.375 us] + change: [-0.8433% -0.3270% +0.2656%] (p = 0.25 > 0.05) + No change in performance detected. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild \ No newline at end of file diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index fd85513c..ff5c37cc 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -10,7 +10,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use maplit::btreemap; use ruma::{ events::{ - pdu::ServerPdu, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -20,7 +19,7 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateMap, StateResolution, StateStore}; +use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; static mut SERVER_TIMESTAMP: i32 = 0; @@ -82,7 +81,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); let state_set_b = [ @@ -95,13 +94,13 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); b.iter(|| { let _resolved = match StateResolution::resolve( &room_id(), - &RoomVersionId::Version2, + &RoomVersionId::Version6, &[state_set_a.clone(), state_set_b.clone()], Some(inner.clone()), &store, @@ -115,7 +114,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { criterion_group!( benches, - // lexico_topo_sort, + lexico_topo_sort, resolution_shallow_auth_chain, resolve_deeper_event_set ); @@ -127,11 +126,11 @@ criterion_main!(benches); // IMPLEMENTATION DETAILS AHEAD // /////////////////////////////////////////////////////////////////////*/ -pub struct TestStore(BTreeMap>); +pub struct TestStore(pub BTreeMap>); #[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) @@ -139,9 +138,9 @@ impl StateStore for TestStore { } } -impl TestStore { +impl TestStore { pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { - let create_event = Arc::new(to_pdu_event::( + let create_event = to_pdu_event::( "CREATE", alice(), EventType::RoomCreate, @@ -149,8 +148,8 @@ impl TestStore { json!({ "creator": alice() }), &[], &[], - )); - let cre = create_event.event_id.clone(); + ); + let cre = create_event.event_id().clone(); self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( @@ -163,7 +162,7 @@ impl TestStore { &[cre.clone()], ); self.0 - .insert(alice_mem.event_id.clone(), Arc::clone(&alice_mem)); + .insert(alice_mem.event_id().clone(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -171,11 +170,11 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id.clone()], - &[alice_mem.event_id.clone()], + &[cre.clone(), alice_mem.event_id().clone()], + &[alice_mem.event_id().clone()], ); self.0 - .insert(join_rules.event_id.clone(), Arc::clone(&join_rules)); + .insert(join_rules.event_id().clone(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -185,11 +184,10 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id.clone()], - &[join_rules.event_id.clone()], + &[cre.clone(), join_rules.event_id().clone()], + &[join_rules.event_id().clone()], ); - self.0 - .insert(bob_mem.event_id.clone(), Arc::clone(&bob_mem)); + self.0.insert(bob_mem.event_id().clone(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -197,20 +195,20 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id.clone()], - &[join_rules.event_id.clone()], + &[cre, join_rules.event_id().clone()], + &[join_rules.event_id().clone()], ); self.0 - .insert(charlie_mem.event_id.clone(), Arc::clone(&charlie_mem)); + .insert(charlie_mem.event_id().clone(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); let expected = [ @@ -221,7 +219,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); (state_at_bob, state_at_charlie, expected) @@ -282,7 +280,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> Arc +) -> Arc where S: AsRef, { @@ -345,7 +343,7 @@ where // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { +fn INITIAL_EVENTS() -> BTreeMap> { vec![ to_pdu_event::( "CREATE", @@ -421,13 +419,13 @@ fn INITIAL_EVENTS() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id.clone(), ev)) + .map(|ev| (ev.event_id().clone(), ev)) .collect() } // all graphs start with these input events #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -467,6 +465,398 @@ fn BAN_STATE_SET() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id.clone(), ev)) + .map(|ev| (ev.event_id().clone(), ev)) .collect() } + +pub mod event { + use std::{collections::BTreeMap, time::SystemTime}; + + use ruma::{ + events::{ + from_raw_json_value, + pdu::{EventHash, Pdu}, + room::member::{MemberEventContent, MembershipState}, + EventDeHelper, EventType, + }, + serde::CanonicalJsonValue, + signatures::reference_hash, + EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, + }; + use serde::{de, ser, Deserialize, Serialize}; + use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; + + use state_res::Event; + + impl Event for StateEvent { + fn event_id(&self) -> &EventId { + self.event_id() + } + + fn room_id(&self) -> &RoomId { + self.room_id() + } + + fn sender(&self) -> &UserId { + self.sender() + } + fn kind(&self) -> EventType { + self.kind() + } + + fn content(&self) -> serde_json::Value { + self.content() + } + fn origin_server_ts(&self) -> SystemTime { + *self.origin_server_ts() + } + + fn state_key(&self) -> Option { + self.state_key() + } + fn prev_events(&self) -> Vec { + self.prev_event_ids() + } + fn depth(&self) -> &UInt { + self.depth() + } + fn auth_events(&self) -> Vec { + self.auth_events() + } + fn redacts(&self) -> Option<&EventId> { + self.redacts() + } + fn hashes(&self) -> &EventHash { + self.hashes() + } + fn signatures(&self) -> BTreeMap, BTreeMap> { + self.signatures() + } + fn unsigned(&self) -> &BTreeMap { + self.unsigned() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + struct EventIdHelper { + event_id: EventId, + } + + /// This feature is turned on in conduit but off when the tests run because + /// we rely on the EventId to check the state-res. + #[cfg(feature = "gen-eventid")] + fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom) + } + + /// Only turned on for testing where we need to keep the ID. + #[cfg(not(feature = "gen-eventid"))] + fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + Ok(match from_raw_json_value::(&json) { + Ok(id) => id.event_id, + Err(_) => { + // panic!("NOT DURING TESTS"); + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom)? + } + }) + } + + // TODO: This no longer needs to be an enum now that PduStub is gone + #[derive(Clone, Debug)] + pub enum StateEvent { + Full(EventId, Pdu), + } + + impl Serialize for StateEvent { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use ser::Error; + use std::convert::TryInto; + + match self { + Self::Full(id, ev) => { + // TODO: do we want to add the eventId when we + // serialize + let val: CanonicalJsonValue = serde_json::to_value(ev) + .map_err(S::Error::custom)? + .try_into() + .map_err(S::Error::custom)?; + + match val { + CanonicalJsonValue::Object(mut obj) => { + obj.insert( + "event_id".into(), + ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, + ); + obj.serialize(serializer) + } + _ => panic!("Pdu not an object"), + } + } + } + } + } + + impl<'de> de::Deserialize<'de> for StateEvent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { + room_id, unsigned, .. + } = from_raw_json_value(&json)?; + + // TODO: do we even want to try for the existing ID + + // Determine whether the event is a full or stub + // based on the fields present. + Ok(if room_id.is_some() { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Full( + event_id(&json)?, + Pdu::RoomV3Pdu(from_raw_json_value(&json)?), + ), + } + } else { + panic!("Found stub event") + }) + } + } + + impl StateEvent { + pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { + Ok(Self::Full( + id, + Pdu::RoomV3Pdu(serde_json::from_value(json)?), + )) + } + + pub fn from_id_canon_obj( + id: EventId, + json: ruma::serde::CanonicalJsonObject, + ) -> Result { + Ok(Self::Full( + id, + // TODO: this is unfortunate (from_value(to_value(json)))... + Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), + )) + } + + pub fn is_power_event(&self) -> bool { + match self { + Self::Full(_, any_event) => match any_event { + Pdu::RoomV1Pdu(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + if let Ok(content) = + // TODO fix clone + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) + { + return event.sender.as_str() + // TODO is None here a failure + != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); + } + } + + false + } + _ => false, + }, + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), + }, + } + } + pub fn deserialize_content( + &self, + ) -> Result { + 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()), + }, + } + } + pub fn origin_server_ts(&self) -> &SystemTime { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, + Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, + }, + } + } + pub fn event_id(&self) -> &EventId { + match self { + // TODO; make this a &EventId + Self::Full(id, _) => id, + } + } + + pub fn sender(&self) -> &UserId { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.sender, + Pdu::RoomV3Pdu(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(), + }, + } + } + + pub fn room_id(&self) -> &RoomId { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.room_id, + Pdu::RoomV3Pdu(ev) => &ev.room_id, + }, + } + } + pub fn kind(&self) -> EventType { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.kind.clone(), + Pdu::RoomV3Pdu(ev) => ev.kind.clone(), + }, + } + } + pub fn state_key(&self) -> Option { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), + Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), + }, + } + } + + #[cfg(not(feature = "unstable-pre-spec"))] + pub fn origin(&self) -> String { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.origin.clone(), + Pdu::RoomV3Pdu(ev) => ev.origin.clone(), + }, + } + } + + pub fn prev_event_ids(&self) -> Vec { + 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(), + }, + } + } + + pub fn auth_events(&self) -> Vec { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), + }, + } + } + + 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(), + }, + } + } + + pub fn unsigned(&self) -> &BTreeMap { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.unsigned, + Pdu::RoomV3Pdu(ev) => &ev.unsigned, + }, + } + } + + pub fn signatures( + &self, + ) -> BTreeMap, BTreeMap> { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, + Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), + }, + } + } + + pub fn hashes(&self) -> &EventHash { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.hashes, + Pdu::RoomV3Pdu(ev) => &ev.hashes, + }, + } + } + + pub fn depth(&self) -> &UInt { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.depth, + Pdu::RoomV3Pdu(ev) => &ev.depth, + }, + } + } + + pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { + 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) + } + }, + } + } + + /// Returns the room version this event is formatted for. + /// + /// Currently either version 1 or 6 is returned, 6 represents + /// version 3 and above. + pub fn room_version(&self) -> RoomVersionId { + // TODO: We have to know the actual room version this is not sufficient + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, + }, + } + } + } +} \ No newline at end of file diff --git a/src/event_auth.rs b/src/event_auth.rs index 271a1fce..25bf4122 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -373,7 +373,7 @@ pub fn valid_membership_change( } Ok(if target_membership == MembershipState::Join { - if user.sender() != target_user_id { + if user.sender() != &target_user_id { false } else if let MembershipState::Ban = current_membership { false @@ -402,7 +402,7 @@ pub fn valid_membership_change( .is_some() } } else if target_membership == MembershipState::Leave { - if user.sender() == target_user_id { + if user.sender() == &target_user_id { current_membership == MembershipState::Join || current_membership == MembershipState::Invite } else if sender_membership != MembershipState::Join @@ -549,7 +549,7 @@ pub fn check_power_levels( } // If the current value is equal to the sender's current power level, reject - if user != &power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { + if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { tracing::warn!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } diff --git a/src/lib.rs b/src/lib.rs index 3c923d66..fde19729 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ mod state_store; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; -pub use state_event::{Event, Requester}; +pub use state_event::Event; pub use state_store::StateStore; // We want to yield to the reactor occasionally during state res when dealing @@ -154,7 +154,7 @@ impl StateResolution { .unwrap(); // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id(), ev))); + event_map.extend(events.into_iter().map(|ev| (ev.event_id().clone(), ev))); // at this point our event_map == store there should be no missing events tracing::debug!("event map size: {}", event_map.len()); @@ -383,7 +383,7 @@ impl StateResolution { // This return value is the key used for sorting events, // events are then sorted by power level, time, // and lexically by event_id. - (-*pl, ev.origin_server_ts(), ev.event_id()) + (-*pl, ev.origin_server_ts(), ev.event_id().clone()) }) } diff --git a/src/state_event.rs b/src/state_event.rs index ab87b56f..0a81eec4 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,29 +1,21 @@ use std::{collections::BTreeMap, time::SystemTime}; use ruma::{ - events::{ - from_raw_json_value, - pdu::{EventHash, Pdu}, - room::member::{MemberEventContent, MembershipState}, - EventDeHelper, EventType, - }, - serde::CanonicalJsonValue, - signatures::{reference_hash, CanonicalJsonObject}, - EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, + events::{pdu::EventHash, EventType}, + EventId, RoomId, ServerName, UInt, UserId, }; -use serde::{de, ser, Deserialize, Serialize}; -use serde_json::value::RawValue as RawJsonValue; +use serde_json::value::Value as JsonValue; /// Abstraction of a PDU so users can have their own PDU types. pub trait Event { /// The `EventId` of this event. - fn event_id(&self) -> EventId; + fn event_id(&self) -> &EventId; /// The `RoomId` of this event. - fn room_id(&self) -> RoomId; + fn room_id(&self) -> &RoomId; /// The `UserId` of this event. - fn sender(&self) -> UserId; + fn sender(&self) -> &UserId; /// The time of creation on the originating server. fn origin_server_ts(&self) -> SystemTime; @@ -41,401 +33,20 @@ pub trait Event { fn prev_events(&self) -> Vec; /// The maximum number of `prev_events` plus 1. - fn depth(&self) -> UInt; + /// + /// This is only used in state resolution version 1. + fn depth(&self) -> &UInt; /// All the authenticating events for this event. fn auth_events(&self) -> Vec; /// If this event is a redaction event this is the event it redacts. - fn redacts(&self) -> Option; + fn redacts(&self) -> Option<&EventId>; /// The `unsigned` content of this event. - fn unsigned(&self) -> CanonicalJsonObject; + fn unsigned(&self) -> &BTreeMap; fn hashes(&self) -> &EventHash; fn signatures(&self) -> BTreeMap, BTreeMap>; } - -#[derive(Clone, Debug, Deserialize, Serialize)] -struct EventIdHelper { - event_id: EventId, -} - -/// This feature is turned on in conduit but off when the tests run because -/// we rely on the EventId to check the state-res. -#[cfg(feature = "gen-eventid")] -fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom) -} - -/// Only turned on for testing where we need to keep the ID. -#[cfg(not(feature = "gen-eventid"))] -fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - Ok(match from_raw_json_value::(&json) { - Ok(id) => id.event_id, - Err(_) => { - // panic!("NOT DURING TESTS"); - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom)? - } - }) -} - -pub struct Requester<'a> { - pub prev_event_ids: Vec, - pub room_id: &'a RoomId, - pub content: &'a serde_json::Value, - pub state_key: Option, - pub sender: &'a UserId, -} - -// TODO: This no longer needs to be an enum now that PduStub is gone -#[derive(Clone, Debug)] -pub enum StateEvent { - Full(EventId, Pdu), -} - -impl Serialize for StateEvent { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - use ser::Error; - use std::convert::TryInto; - - match self { - Self::Full(id, ev) => { - // TODO: do we want to add the eventId when we - // serialize - let val: CanonicalJsonValue = serde_json::to_value(ev) - .map_err(S::Error::custom)? - .try_into() - .map_err(S::Error::custom)?; - - match val { - CanonicalJsonValue::Object(mut obj) => { - obj.insert( - "event_id".into(), - ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, - ); - obj.serialize(serializer) - } - _ => panic!("Pdu not an object"), - } - } - } - } -} - -impl<'de> de::Deserialize<'de> for StateEvent { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let json = Box::::deserialize(deserializer)?; - let EventDeHelper { - room_id, unsigned, .. - } = from_raw_json_value(&json)?; - - // TODO: do we even want to try for the existing ID - - // Determine whether the event is a full or stub - // based on the fields present. - Ok(if room_id.is_some() { - match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Full( - event_id(&json)?, - Pdu::RoomV3Pdu(from_raw_json_value(&json)?), - ), - } - } else { - panic!("Found stub event") - }) - } -} - -impl StateEvent { - pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { - Ok(Self::Full( - id, - Pdu::RoomV3Pdu(serde_json::from_value(json)?), - )) - } - - pub fn from_id_canon_obj( - id: EventId, - json: ruma::serde::CanonicalJsonObject, - ) -> Result { - Ok(Self::Full( - id, - // TODO: this is unfortunate (from_value(to_value(json)))... - Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), - )) - } - - pub fn to_requester(&self) -> Requester<'_> { - Requester { - prev_event_ids: self.prev_event_ids(), - room_id: self.room_id(), - content: self.content(), - state_key: Some(self.state_key()), - sender: self.sender(), - } - } - - pub fn is_power_event(&self) -> bool { - match self { - Self::Full(_, any_event) => match any_event { - Pdu::RoomV1Pdu(event) => match event.kind { - EventType::RoomPowerLevels - | EventType::RoomJoinRules - | EventType::RoomCreate => event.state_key == Some("".into()), - EventType::RoomMember => { - if let Ok(content) = - // TODO fix clone - serde_json::from_value::(event.content.clone()) - { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() - // TODO is None here a failure - != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); - } - } - - false - } - _ => false, - }, - Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), - }, - } - } - pub fn deserialize_content( - &self, - ) -> Result { - 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()), - }, - } - } - pub fn origin_server_ts(&self) -> &SystemTime { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, - Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, - }, - } - } - pub fn event_id(&self) -> EventId { - match self { - // TODO; make this a &EventId - Self::Full(id, _) => id.clone(), - } - } - - pub fn sender(&self) -> &UserId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.sender, - Pdu::RoomV3Pdu(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(), - }, - } - } - - pub fn room_id(&self) -> &RoomId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.room_id, - Pdu::RoomV3Pdu(ev) => &ev.room_id, - }, - } - } - pub fn kind(&self) -> EventType { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.kind.clone(), - Pdu::RoomV3Pdu(ev) => ev.kind.clone(), - }, - } - } - pub fn state_key(&self) -> String { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), - Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), - }, - } - .expect("All state events have a state key") - } - - #[cfg(not(feature = "unstable-pre-spec"))] - pub fn origin(&self) -> String { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.origin.clone(), - Pdu::RoomV3Pdu(ev) => ev.origin.clone(), - }, - } - } - - pub fn prev_event_ids(&self) -> Vec { - 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(), - }, - } - } - - pub fn auth_events(&self) -> Vec { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), - Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), - }, - } - } - - pub fn content(&self) -> &serde_json::Value { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.content, - Pdu::RoomV3Pdu(ev) => &ev.content, - }, - } - } - - pub fn unsigned(&self) -> &BTreeMap { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.unsigned, - Pdu::RoomV3Pdu(ev) => &ev.unsigned, - }, - } - } - - pub fn signatures( - &self, - ) -> BTreeMap, BTreeMap> { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, - Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), - }, - } - } - - pub fn hashes(&self) -> &EventHash { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.hashes, - Pdu::RoomV3Pdu(ev) => &ev.hashes, - }, - } - } - - pub fn depth(&self) -> &UInt { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.depth, - Pdu::RoomV3Pdu(ev) => &ev.depth, - }, - } - } - - pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { - 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) - } - }, - } - } - - /// Returns the room version this event is formatted for. - /// - /// Currently either version 1 or 6 is returned, 6 represents - /// version 3 and above. - pub fn room_version(&self) -> RoomVersionId { - // TODO: We have to know the actual room version this is not sufficient - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, - Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialize_pdu() { - let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; - - let pdu = serde_json::from_str::(non_canonical_json).unwrap(); - - assert_eq!( - match &pdu { - StateEvent::Full(id, _) => id, - }, - &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") - ); - } - - #[test] - fn serialize_pdu() { - let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; - - let pdu = serde_json::from_str::(non_canonical_json).unwrap(); - - assert_eq!( - match &pdu { - StateEvent::Full(id, _) => id, - }, - &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") - ); - - // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing - // For testing we must serialize the PDU with the `event_id` field this is probably not correct for production - // although without them we get "Invalid bytes in DB" errors in conduit - assert_eq!( - ruma::serde::to_canonical_json_string(&pdu).unwrap(), - r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"event_id":"$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU","hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, - ) - } -} diff --git a/src/state_store.rs b/src/state_store.rs index 9f863fca..50dad0ea 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,9 +1,6 @@ use std::{collections::BTreeSet, sync::Arc}; -use ruma::{ - events::pdu::ServerPdu, - identifiers::{EventId, RoomId}, -}; +use ruma::identifiers::{EventId, RoomId}; use crate::{Event, Result}; diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 7fa976ad..fb6989b7 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -6,11 +6,11 @@ use state_res::{ // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, valid_membership_change, }, - Requester, StateMap + StateMap }; mod utils; -use utils::{alice, charlie, event_id, member_content_ban, room_id, INITIAL_EVENTS}; +use utils::{alice, charlie, event_id, member_content_ban, room_id, to_pdu_event, INITIAL_EVENTS}; #[test] fn test_ban_pass() { @@ -18,23 +18,25 @@ fn test_ban_pass() { let prev = events .values() - .find(|ev| ev.event_id.as_str().contains("IMC")) + .find(|ev| ev.event_id().as_str().contains("IMC")) .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), Arc::clone(ev))) + .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) .collect::>(); - let requester = Requester { - prev_event_ids: vec![event_id("IMC")], - room_id: &room_id(), - content: &member_content_ban(), - state_key: Some(charlie().to_string()), - sender: &alice(), - }; + let requester = to_pdu_event( + "HELLO", + alice(), + ruma::events::EventType::RoomMember, + Some(charlie().as_str()), + member_content_ban(), + &vec![], + &vec![event_id("IMC")], + ); - assert!(valid_membership_change(requester, prev, None, &auth_events).unwrap()) + assert!(valid_membership_change(&requester, prev, None, &auth_events).unwrap()) } #[test] @@ -43,21 +45,23 @@ fn test_ban_fail() { let prev = events .values() - .find(|ev| ev.event_id.as_str().contains("IMC")) + .find(|ev| ev.event_id().as_str().contains("IMC")) .map(Arc::clone); let auth_events = events .values() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), Arc::clone(ev))) + .map(|ev| ((ev.kind(), ev.state_key()), Arc::clone(ev))) .collect::>(); - let requester = Requester { - prev_event_ids: vec![event_id("IMC")], - room_id: &room_id(), - content: &member_content_ban(), - state_key: Some(alice().to_string()), - sender: &charlie(), - }; + let requester = to_pdu_event( + "HELLO", + charlie(), + ruma::events::EventType::RoomMember, + Some(alice().as_str()), + member_content_ban(), + &vec![], + &vec![event_id("IMC")], + ); - assert!(!valid_membership_change(requester, prev, None, &auth_events).unwrap()) + assert!(!valid_membership_change(&requester, prev, None, &auth_events).unwrap()) } diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 02d74b3a..168cf03e 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -4,7 +4,7 @@ use ruma::{ events::EventType, identifiers::{EventId, RoomVersionId}, }; -use state_res::{is_power_event, StateMap}; +use state_res::{is_power_event, Event, StateMap}; mod utils; use utils::{room_id, TestStore, INITIAL_EVENTS}; @@ -25,7 +25,7 @@ fn test_event_sort() { let event_map = events .values() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) .collect::>(); let auth_chain = &[] as &[_]; @@ -33,7 +33,7 @@ fn test_event_sort() { let power_events = event_map .values() .filter(|pdu| is_power_event(&pdu)) - .map(|pdu| pdu.event_id.clone()) + .map(|pdu| pdu.event_id().clone()) .collect::>(); // This is a TODO in conduit diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 68a72837..cc123208 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, sync::Arc}; use ruma::{ - events::{pdu::ServerPdu, EventType}, + events::EventType, identifiers::{EventId, RoomVersionId}, }; use serde_json::json; @@ -12,7 +12,7 @@ use state_res::{StateMap, StateResolution}; mod utils; use utils::{ alice, bob, do_check, ella, event_id, member_content_ban, member_content_join, room_id, - to_pdu_event, zara, TestStore, INITIAL_EVENTS, + to_pdu_event, zara, StateEvent, TestStore, INITIAL_EVENTS, }; #[test] @@ -50,7 +50,7 @@ fn base_with_auth_chains() { let resolved = resolved .values() .cloned() - .chain(INITIAL_EVENTS().values().map(|e| e.event_id.clone())) + .chain(INITIAL_EVENTS().values().map(|e| e.event_id().clone())) .collect::>(); let expected = vec![ @@ -89,7 +89,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); let state_set_b = [ @@ -102,7 +102,7 @@ fn ban_with_auth_chains2() { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind.clone(), ev.state_key.clone()), ev.event_id.clone())) + .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); let resolved: StateMap = match StateResolution::resolve( @@ -164,7 +164,7 @@ fn join_rule_with_auth_chain() { } #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -204,12 +204,12 @@ fn BAN_STATE_SET() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id.clone(), ev)) + .map(|ev| (ev.event_id().clone(), ev)) .collect() } #[allow(non_snake_case)] -fn JOIN_RULE() -> BTreeMap> { +fn JOIN_RULE() -> BTreeMap> { vec![ to_pdu_event( "JR", @@ -231,6 +231,6 @@ fn JOIN_RULE() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id.clone(), ev)) + .map(|ev| (ev.event_id().clone(), ev)) .collect() } diff --git a/tests/state_res.rs b/tests/state_res.rs index 4017ee0d..044d0811 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -12,7 +12,7 @@ use tracing_subscriber as tracer; mod utils; use utils::{ alice, bob, charlie, do_check, ella, event_id, member_content_ban, member_content_join, - room_id, to_init_pdu_event, to_pdu_event, zara, TestStore, LOGGER, + room_id, to_init_pdu_event, to_pdu_event, zara, StateEvent, TestStore, LOGGER, }; #[test] @@ -260,7 +260,7 @@ fn topic_setting() { #[test] fn test_event_map_none() { - let mut store = TestStore(btreemap! {}); + let mut store = TestStore::(btreemap! {}); // build up the DAG let (state_at_bob, state_at_charlie, expected) = store.set_up(); @@ -304,7 +304,7 @@ fn test_lexicographical_sort() { // A StateStore implementation for testing // // -impl TestStore { +impl TestStore { pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { // to activate logging use `RUST_LOG=debug cargo t one_test_only` let _ = LOGGER.call_once(|| { @@ -321,7 +321,7 @@ impl TestStore { &[], &[], ); - let cre = create_event.event_id.clone(); + let cre = create_event.event_id().clone(); self.0.insert(cre.clone(), Arc::clone(&create_event)); let alice_mem = to_pdu_event( @@ -334,7 +334,7 @@ impl TestStore { &[cre.clone()], ); self.0 - .insert(alice_mem.event_id.clone(), Arc::clone(&alice_mem)); + .insert(alice_mem.event_id().clone(), Arc::clone(&alice_mem)); let join_rules = to_pdu_event( "IJR", @@ -342,11 +342,11 @@ impl TestStore { EventType::RoomJoinRules, Some(""), json!({ "join_rule": JoinRule::Public }), - &[cre.clone(), alice_mem.event_id.clone()], - &[alice_mem.event_id.clone()], + &[cre.clone(), alice_mem.event_id().clone()], + &[alice_mem.event_id().clone()], ); self.0 - .insert(join_rules.event_id.clone(), join_rules.clone()); + .insert(join_rules.event_id().clone(), join_rules.clone()); // Bob and Charlie join at the same time, so there is a fork // this will be represented in the state_sets when we resolve @@ -356,10 +356,10 @@ impl TestStore { EventType::RoomMember, Some(bob().to_string().as_str()), member_content_join(), - &[cre.clone(), join_rules.event_id.clone()], - &[join_rules.event_id.clone()], + &[cre.clone(), join_rules.event_id().clone()], + &[join_rules.event_id().clone()], ); - self.0.insert(bob_mem.event_id.clone(), bob_mem.clone()); + self.0.insert(bob_mem.event_id().clone(), bob_mem.clone()); let charlie_mem = to_pdu_event( "IMC", @@ -367,20 +367,20 @@ impl TestStore { EventType::RoomMember, Some(charlie().to_string().as_str()), member_content_join(), - &[cre, join_rules.event_id.clone()], - &[join_rules.event_id.clone()], + &[cre, join_rules.event_id().clone()], + &[join_rules.event_id().clone()], ); self.0 - .insert(charlie_mem.event_id.clone(), charlie_mem.clone()); + .insert(charlie_mem.event_id().clone(), charlie_mem.clone()); let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); let expected = [ @@ -391,7 +391,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind.clone(), e.state_key.clone()), e.event_id.clone())) + .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) .collect::>(); (state_at_bob, state_at_charlie, expected) diff --git a/tests/utils.rs b/tests/utils.rs index 3195f70f..bed80160 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -9,7 +9,6 @@ use std::{ use ruma::{ events::{ - pdu::ServerPdu, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -19,15 +18,17 @@ use ruma::{ identifiers::{EventId, RoomId, RoomVersionId, UserId}, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Result, StateMap, StateResolution, StateStore}; +use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; use tracing_subscriber as tracer; +pub use event::StateEvent; + pub static LOGGER: Once = Once::new(); static mut SERVER_TIMESTAMP: i32 = 0; pub fn do_check( - events: &[Arc], + events: &[Arc], edges: Vec>, expected_state_ids: Vec, ) { @@ -42,20 +43,20 @@ pub fn do_check( INITIAL_EVENTS() .values() .chain(events) - .map(|ev| (ev.event_id.clone(), ev.clone())) + .map(|ev| (ev.event_id().clone(), ev.clone())) .collect(), ); // This will be lexi_topo_sorted for resolution let mut graph = BTreeMap::new(); - // this is the same as in `resolve` event_id -> ServerPdu + // this is the same as in `resolve` event_id -> StateEvent let mut fake_event_map = BTreeMap::new(); // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... for ev in INITIAL_EVENTS().values().chain(events) { - graph.insert(ev.event_id.clone(), vec![]); - fake_event_map.insert(ev.event_id.clone(), ev.clone()); + graph.insert(ev.event_id().clone(), vec![]); + fake_event_map.insert(ev.event_id().clone(), ev.clone()); } for pair in INITIAL_EDGES().windows(2) { @@ -72,8 +73,8 @@ pub fn do_check( } } - // event_id -> ServerPdu - let mut event_map: BTreeMap> = BTreeMap::new(); + // event_id -> StateEvent + let mut event_map: BTreeMap> = BTreeMap::new(); // event_id -> StateMap let mut state_at_event: BTreeMap> = BTreeMap::new(); @@ -83,7 +84,7 @@ pub fn do_check( StateResolution::lexicographical_topological_sort(&graph, |id| (0, UNIX_EPOCH, id.clone())) { let fake_event = fake_event_map.get(&node).unwrap(); - let event_id = fake_event.event_id.clone(); + let event_id = fake_event.event_id().clone(); let prev_events = graph.get(&node).unwrap(); @@ -124,17 +125,17 @@ pub fn do_check( let mut state_after = state_before.clone(); - if fake_event.state_key.is_some() { - let ty = fake_event.kind.clone(); - let key = fake_event.state_key.clone(); + if fake_event.state_key().is_some() { + let ty = fake_event.kind(); + let key = fake_event.state_key(); state_after.insert((ty, key), event_id.clone()); } let auth_types = state_res::auth_types_for_event( - &fake_event.kind, - &fake_event.sender, - fake_event.state_key.clone(), - fake_event.content.clone(), + &fake_event.kind(), + fake_event.sender(), + fake_event.state_key(), + fake_event.content(), ); let mut auth_events = vec![]; @@ -147,13 +148,13 @@ pub fn do_check( // TODO The event is just remade, adding the auth_events and prev_events here // the `to_pdu_event` was split into `init` and the fn below, could be better let e = fake_event; - let ev_id = e.event_id.clone(); + let ev_id = e.event_id().clone(); let event = to_pdu_event( - &e.event_id.clone().to_string(), - e.sender.clone(), - e.kind.clone(), - e.state_key.as_deref(), - e.content.clone(), + e.event_id().as_str(), + e.sender().clone(), + e.kind().clone(), + e.state_key().as_deref(), + e.content(), &auth_events, prev_events, ); @@ -205,7 +206,7 @@ pub fn do_check( .collect::>(), )); - let key = (ev.kind.clone(), ev.state_key.clone()); + let key = (ev.kind(), ev.state_key()); expected_state.insert(key, node); } @@ -223,11 +224,11 @@ pub fn do_check( assert_eq!(expected_state, end_state); } -pub struct TestStore(pub BTreeMap>); +pub struct TestStore(pub BTreeMap>); #[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) @@ -290,7 +291,7 @@ pub fn to_init_pdu_event( ev_type: EventType, state_key: Option<&str>, content: JsonValue, -) -> Arc { +) -> Arc { let ts = unsafe { let ts = SERVER_TIMESTAMP; // increment the "origin_server_ts" value @@ -346,7 +347,7 @@ pub fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> Arc +) -> Arc where S: AsRef, { @@ -409,7 +410,7 @@ where // all graphs start with these input events #[allow(non_snake_case)] -pub fn INITIAL_EVENTS() -> BTreeMap> { +pub fn INITIAL_EVENTS() -> BTreeMap> { // this is always called so we can init the logger here let _ = LOGGER.call_once(|| { tracer::fmt() @@ -492,7 +493,7 @@ pub fn INITIAL_EVENTS() -> BTreeMap> { ), ] .into_iter() - .map(|ev| (ev.event_id.clone(), ev)) + .map(|ev| (ev.event_id().clone(), ev)) .collect() } @@ -503,3 +504,437 @@ pub fn INITIAL_EDGES() -> Vec { .map(event_id) .collect::>() } + +pub mod event { + use std::{collections::BTreeMap, time::SystemTime}; + + use ruma::{ + events::{ + from_raw_json_value, + pdu::{EventHash, Pdu}, + room::member::{MemberEventContent, MembershipState}, + EventDeHelper, EventType, + }, + serde::CanonicalJsonValue, + signatures::reference_hash, + EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, + }; + use serde::{de, ser, Deserialize, Serialize}; + use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; + + use state_res::Event; + + impl Event for StateEvent { + fn event_id(&self) -> &EventId { + self.event_id() + } + + fn room_id(&self) -> &RoomId { + self.room_id() + } + + fn sender(&self) -> &UserId { + self.sender() + } + fn kind(&self) -> EventType { + self.kind() + } + + fn content(&self) -> serde_json::Value { + self.content() + } + fn origin_server_ts(&self) -> SystemTime { + *self.origin_server_ts() + } + + fn state_key(&self) -> Option { + self.state_key() + } + fn prev_events(&self) -> Vec { + self.prev_event_ids() + } + fn depth(&self) -> &UInt { + self.depth() + } + fn auth_events(&self) -> Vec { + self.auth_events() + } + fn redacts(&self) -> Option<&EventId> { + self.redacts() + } + fn hashes(&self) -> &EventHash { + self.hashes() + } + fn signatures(&self) -> BTreeMap, BTreeMap> { + self.signatures() + } + fn unsigned(&self) -> &BTreeMap { + self.unsigned() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + struct EventIdHelper { + event_id: EventId, + } + + /// This feature is turned on in conduit but off when the tests run because + /// we rely on the EventId to check the state-res. + #[cfg(feature = "gen-eventid")] + fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom) + } + + /// Only turned on for testing where we need to keep the ID. + #[cfg(not(feature = "gen-eventid"))] + fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + Ok(match from_raw_json_value::(&json) { + Ok(id) => id.event_id, + Err(_) => { + // panic!("NOT DURING TESTS"); + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom)? + } + }) + } + + // TODO: This no longer needs to be an enum now that PduStub is gone + #[derive(Clone, Debug)] + pub enum StateEvent { + Full(EventId, Pdu), + } + + impl Serialize for StateEvent { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use ser::Error; + use std::convert::TryInto; + + match self { + Self::Full(id, ev) => { + // TODO: do we want to add the eventId when we + // serialize + let val: CanonicalJsonValue = serde_json::to_value(ev) + .map_err(S::Error::custom)? + .try_into() + .map_err(S::Error::custom)?; + + match val { + CanonicalJsonValue::Object(mut obj) => { + obj.insert( + "event_id".into(), + ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, + ); + obj.serialize(serializer) + } + _ => panic!("Pdu not an object"), + } + } + } + } + } + + impl<'de> de::Deserialize<'de> for StateEvent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { + room_id, unsigned, .. + } = from_raw_json_value(&json)?; + + // TODO: do we even want to try for the existing ID + + // Determine whether the event is a full or stub + // based on the fields present. + Ok(if room_id.is_some() { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Full( + event_id(&json)?, + Pdu::RoomV3Pdu(from_raw_json_value(&json)?), + ), + } + } else { + panic!("Found stub event") + }) + } + } + + impl StateEvent { + pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { + Ok(Self::Full( + id, + Pdu::RoomV3Pdu(serde_json::from_value(json)?), + )) + } + + pub fn from_id_canon_obj( + id: EventId, + json: ruma::serde::CanonicalJsonObject, + ) -> Result { + Ok(Self::Full( + id, + // TODO: this is unfortunate (from_value(to_value(json)))... + Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), + )) + } + + pub fn is_power_event(&self) -> bool { + match self { + Self::Full(_, any_event) => match any_event { + Pdu::RoomV1Pdu(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + if let Ok(content) = + // TODO fix clone + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) + { + return event.sender.as_str() + // TODO is None here a failure + != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); + } + } + + false + } + _ => false, + }, + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), + }, + } + } + pub fn deserialize_content( + &self, + ) -> Result { + 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()), + }, + } + } + pub fn origin_server_ts(&self) -> &SystemTime { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, + Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, + }, + } + } + pub fn event_id(&self) -> &EventId { + match self { + // TODO; make this a &EventId + Self::Full(id, _) => id, + } + } + + pub fn sender(&self) -> &UserId { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.sender, + Pdu::RoomV3Pdu(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(), + }, + } + } + + pub fn room_id(&self) -> &RoomId { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.room_id, + Pdu::RoomV3Pdu(ev) => &ev.room_id, + }, + } + } + pub fn kind(&self) -> EventType { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.kind.clone(), + Pdu::RoomV3Pdu(ev) => ev.kind.clone(), + }, + } + } + pub fn state_key(&self) -> Option { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), + Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), + }, + } + } + + #[cfg(not(feature = "unstable-pre-spec"))] + pub fn origin(&self) -> String { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.origin.clone(), + Pdu::RoomV3Pdu(ev) => ev.origin.clone(), + }, + } + } + + pub fn prev_event_ids(&self) -> Vec { + 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(), + }, + } + } + + pub fn auth_events(&self) -> Vec { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), + }, + } + } + + 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(), + }, + } + } + + pub fn unsigned(&self) -> &BTreeMap { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.unsigned, + Pdu::RoomV3Pdu(ev) => &ev.unsigned, + }, + } + } + + pub fn signatures( + &self, + ) -> BTreeMap, BTreeMap> { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, + Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), + }, + } + } + + pub fn hashes(&self) -> &EventHash { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.hashes, + Pdu::RoomV3Pdu(ev) => &ev.hashes, + }, + } + } + + pub fn depth(&self) -> &UInt { + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(ev) => &ev.depth, + Pdu::RoomV3Pdu(ev) => &ev.depth, + }, + } + } + + pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { + 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) + } + }, + } + } + + /// Returns the room version this event is formatted for. + /// + /// Currently either version 1 or 6 is returned, 6 represents + /// version 3 and above. + pub fn room_version(&self) -> RoomVersionId { + // TODO: We have to know the actual room version this is not sufficient + match self { + Self::Full(_, ev) => match ev { + Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, + }, + } + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn deserialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; + + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + } + + #[test] + fn serialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; + + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + + // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing + // For testing we must serialize the PDU with the `event_id` field this is probably not correct for production + // although without them we get "Invalid bytes in DB" errors in conduit + assert_eq!( + ruma::serde::to_canonical_json_string(&pdu).unwrap(), + r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"event_id":"$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU","hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, + ) + } + } + +} \ No newline at end of file From 94be5b0fef83e3bec5145e2191e359b96c5f3ba3 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 30 Dec 2020 16:32:38 -0500 Subject: [PATCH 087/130] Update ruma and serde to latest --- Cargo.toml | 8 ++++---- src/event_auth.rs | 9 ++++----- src/lib.rs | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 08874472..e196720a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,17 +13,17 @@ repository = "https://github.com/ruma/state-res" [dependencies] itertools = "0.9.0" -serde = { version = "1.0.117", features = ["derive"] } +serde = { version = "1.0.118", features = ["derive"] } serde_json = "1.0.60" tracing = "0.1.22" maplit = "1.0.2" thiserror = "1.0.22" [dependencies.ruma] -git = "https://github.com/DevinR528/ruma" -branch = "server-pdu" +git = "https://github.com/ruma/ruma" +# branch = "server-pdu" # path = "../__forks__/ruma/ruma" -# rev = "45d01011554f9d07739e9a5edf5498d8ac16f273" +rev = "210b6dd823ba89c5a44c3c9d913d377c4b54c896" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] [features] diff --git a/src/event_auth.rs b/src/event_auth.rs index 25bf4122..74b363ce 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -3,7 +3,6 @@ use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; use maplit::btreeset; use ruma::{ events::{ - pdu::ServerPdu, room::{ self, join_rules::JoinRule, @@ -660,10 +659,10 @@ pub fn check_redaction( /// Check that the member event matches `state`. /// /// This function returns false instead of failing when deserialization fails. -pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { +pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { if let Some(event) = member_event { if let Ok(content) = - serde_json::from_value::(event.content.clone()) + serde_json::from_value::(event.content()) { content.membership == state } else { @@ -675,10 +674,10 @@ pub fn check_membership(member_event: Option>, state: MembershipS } /// Can this room federate based on its m.room.create event. -pub fn can_federate(auth_events: &StateMap>) -> bool { +pub fn can_federate(auth_events: &StateMap>) -> bool { let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); if let Some(ev) = creation_event { - if let Some(fed) = ev.content.get("m.federate") { + if let Some(fed) = ev.content().get("m.federate") { fed == "true" } else { false diff --git a/src/lib.rs b/src/lib.rs index fde19729..f2c92753 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use maplit::btreeset; use ruma::{ - events::{pdu::ServerPdu, EventType}, + events::EventType, identifiers::{EventId, RoomId, RoomVersionId}, }; From b0ee71e40176ff4062a87ac1a6367509139d9020 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 31 Dec 2020 15:53:08 -0500 Subject: [PATCH 088/130] Make event_map &mut and take fields in membership_change --- benches/state_res_bench.rs | 5 +++-- src/event_auth.rs | 38 ++++++++++++++++++++++++-------------- src/lib.rs | 25 +++++++------------------ tests/event_auth.rs | 20 ++++++++++++++++++-- tests/res_with_auth_ids.rs | 19 +++++++++++++------ tests/state_res.rs | 3 ++- tests/utils.rs | 2 +- 7 files changed, 68 insertions(+), 44 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index ff5c37cc..86d55ff1 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -48,11 +48,12 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { let (state_at_bob, state_at_charlie, _) = store.set_up(); b.iter(|| { + let mut ev_map = state_res::EventMap::default(); let _resolved = match StateResolution::resolve( &room_id(), &RoomVersionId::Version6, &[state_at_bob.clone(), state_at_charlie.clone()], - None, + &mut ev_map, &store, ) { Ok(state) => state, @@ -102,7 +103,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { &room_id(), &RoomVersionId::Version6, &[state_set_a.clone(), state_set_b.clone()], - Some(inner.clone()), + &mut inner, &store, ) { Ok(state) => state, diff --git a/src/event_auth.rs b/src/event_auth.rs index 74b363ce..eaf6ef57 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -193,7 +193,9 @@ pub fn auth_check( } if !valid_membership_change( - incoming_event, + incoming_event.state_key().as_deref(), + incoming_event.sender(), + incoming_event.content(), prev_event, current_third_party_invite, &auth_events, @@ -271,25 +273,27 @@ pub fn auth_check( /// this is generated by calling `auth_types_for_event` with the membership event and /// the current State. pub fn valid_membership_change( - user: &Arc, + user_state_key: Option<&str>, + user_sender: &UserId, + content: serde_json::Value, prev_event: Option>, current_third_party_invite: Option>, auth_events: &StateMap>, ) -> Result { - let state_key = if let Some(s) = user.state_key() { + let state_key = if let Some(s) = user_state_key { s } else { return Err(Error::InvalidPdu("State event requires state_key".into())); }; - let content = serde_json::from_value::(user.content())?; + let content = serde_json::from_value::(content)?; let target_membership = content.membership; - let target_user_id = UserId::try_from(state_key.as_str()) - .map_err(|e| Error::ConversionError(format!("{}", e)))?; + let target_user_id = + UserId::try_from(state_key).map_err(|e| Error::ConversionError(format!("{}", e)))?; - let key = (EventType::RoomMember, Some(user.sender().to_string())); + let key = (EventType::RoomMember, Some(user_sender.to_string())); let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { @@ -333,7 +337,7 @@ pub fn valid_membership_change( }, )?; - let sender_power = power_levels.users.get(&user.sender()).map_or_else( + let sender_power = power_levels.users.get(user_sender).map_or_else( || { if sender_membership != member::MembershipState::Join { None @@ -372,7 +376,7 @@ pub fn valid_membership_change( } Ok(if target_membership == MembershipState::Join { - if user.sender() != &target_user_id { + if user_sender != &target_user_id { false } else if let MembershipState::Ban = current_membership { false @@ -388,7 +392,12 @@ pub fn valid_membership_change( if current_membership == MembershipState::Ban { false } else { - verify_third_party_invite(user, &tp_id, current_third_party_invite) + verify_third_party_invite( + Some(state_key), + user_sender, + &tp_id, + current_third_party_invite, + ) } } else if sender_membership != MembershipState::Join || current_membership == MembershipState::Join @@ -401,7 +410,7 @@ pub fn valid_membership_change( .is_some() } } else if target_membership == MembershipState::Leave { - if user.sender() == &target_user_id { + if user_sender == &target_user_id { current_membership == MembershipState::Join || current_membership == MembershipState::Invite } else if sender_membership != MembershipState::Join @@ -791,14 +800,15 @@ pub fn can_send_invite(event: &Arc, auth_events: &StateMap>) } pub fn verify_third_party_invite( - event: &Arc, + user_state_key: Option<&str>, + sender: &UserId, tp_id: &member::ThirdPartyInvite, current_third_party_invite: Option>, ) -> bool { // 1. check for user being banned happens before this is called // checking for mxid and token keys is done by ruma when deserializing - if event.state_key() != Some(tp_id.signed.mxid.to_string()) { + if user_state_key != Some(tp_id.signed.mxid.as_str()) { return false; } @@ -809,7 +819,7 @@ pub fn verify_third_party_invite( return false; } - if event.sender() != current_tpid.sender() { + if sender != current_tpid.sender() { return false; } diff --git a/src/lib.rs b/src/lib.rs index f2c92753..de68bac3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,17 +46,12 @@ impl StateResolution { room_version: &RoomVersionId, incoming_event: Arc, current_state: &StateMap, - event_map: Option>>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Result { tracing::info!("Applying a single event, state resolution starting"); let ev = incoming_event; - let mut event_map = if let Some(ev_map) = event_map { - ev_map - } else { - EventMap::new() - }; let prev_event = if let Some(id) = ev.prev_events().first() { store.get_event(room_id, id).ok() } else { @@ -69,7 +64,7 @@ impl StateResolution { { if let Some(ev_id) = current_state.get(&key) { if let Some(event) = - StateResolution::get_or_load_event(room_id, ev_id, &mut event_map, store) + StateResolution::get_or_load_event(room_id, ev_id, event_map, store) { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); @@ -102,17 +97,11 @@ impl StateResolution { room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], - // TODO: make the `Option<&mut EventMap>>` - event_map: Option>>, + event_map: &mut EventMap>, store: &dyn StateStore, ) -> Result> { tracing::info!("State resolution starting"); - let mut event_map = if let Some(ev_map) = event_map { - ev_map - } else { - EventMap::new() - }; // split non-conflicting and conflicting state let (clean, conflicting) = StateResolution::separate(&state_sets); @@ -178,7 +167,7 @@ impl StateResolution { let mut sorted_control_levels = StateResolution::reverse_topological_power_sort( room_id, &control_events, - &mut event_map, + event_map, store, &all_conflicted, ); @@ -197,7 +186,7 @@ impl StateResolution { room_version, &sorted_control_levels, &clean, - &mut event_map, + event_map, store, )?; @@ -238,7 +227,7 @@ impl StateResolution { room_id, &events_to_resolve, power_event, - &mut event_map, + event_map, store, ); @@ -255,7 +244,7 @@ impl StateResolution { room_version, &sorted_left_events, &resolved_control, // The control events are added to the final resolved state - &mut event_map, + event_map, store, )?; diff --git a/tests/event_auth.rs b/tests/event_auth.rs index fb6989b7..46270ca7 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -36,7 +36,15 @@ fn test_ban_pass() { &vec![event_id("IMC")], ); - assert!(valid_membership_change(&requester, prev, None, &auth_events).unwrap()) + assert!(valid_membership_change( + requester.state_key().as_deref(), + requester.sender(), + requester.content(), + prev, + None, + &auth_events + ) + .unwrap()) } #[test] @@ -63,5 +71,13 @@ fn test_ban_fail() { &vec![event_id("IMC")], ); - assert!(!valid_membership_change(&requester, prev, None, &auth_events).unwrap()) + assert!(!valid_membership_change( + requester.state_key().as_deref(), + requester.sender(), + requester.content(), + prev, + None, + &auth_events + ) + .unwrap()) } diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index cc123208..627750e2 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -41,11 +41,17 @@ fn ban_with_auth_chains() { fn base_with_auth_chains() { let store = TestStore(INITIAL_EVENTS()); - let resolved: BTreeMap<_, EventId> = - match StateResolution::resolve(&room_id(), &RoomVersionId::Version6, &[], None, &store) { - Ok(state) => state, - Err(e) => panic!("{}", e), - }; + let mut ev_map = state_res::EventMap::default(); + let resolved: BTreeMap<_, EventId> = match StateResolution::resolve( + &room_id(), + &RoomVersionId::Version6, + &[], + &mut ev_map, + &store, + ) { + Ok(state) => state, + Err(e) => panic!("{}", e), + }; let resolved = resolved .values() @@ -105,11 +111,12 @@ fn ban_with_auth_chains2() { .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); + let mut ev_map = state_res::EventMap::default(); let resolved: StateMap = match StateResolution::resolve( &room_id(), &RoomVersionId::Version6, &[state_set_a, state_set_b], - None, + &mut ev_map, &store, ) { Ok(state) => state, diff --git a/tests/state_res.rs b/tests/state_res.rs index 044d0811..f85f7eec 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -265,11 +265,12 @@ fn test_event_map_none() { // build up the DAG let (state_at_bob, state_at_charlie, expected) = store.set_up(); + let mut ev_map = state_res::EventMap::default(); let resolved = match StateResolution::resolve( &room_id(), &RoomVersionId::Version2, &[state_at_bob, state_at_charlie], - None, + &mut ev_map, &store, ) { Ok(state) => state, diff --git a/tests/utils.rs b/tests/utils.rs index bed80160..bec72356 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -114,7 +114,7 @@ pub fn do_check( &room_id(), &RoomVersionId::Version6, &state_sets, - Some(event_map.clone()), + &mut event_map, &store, ); match resolved { From f4772e0fcb1fd37e1f4c0ac7bb6b8075198ce6a0 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 3 Jan 2021 17:44:24 -0500 Subject: [PATCH 089/130] Make auth_events arg for auth_check be a ref, cleanup --- src/event_auth.rs | 23 ++++------------------- src/lib.rs | 4 ++-- tests/event_auth.rs | 10 +++++----- tests/event_sorting.rs | 2 +- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index eaf6ef57..29c0e703 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -71,7 +71,7 @@ pub fn auth_check( room_version: &RoomVersionId, incoming_event: &Arc, prev_event: Option>, - auth_events: StateMap>, + auth_events: &StateMap>, current_third_party_invite: Option>, ) -> Result { tracing::info!("auth_check beginning for {}", incoming_event.kind()); @@ -315,22 +315,7 @@ pub fn valid_membership_change( let key = (EventType::RoomPowerLevels, Some("".into())); let power_levels = auth_events.get(&key).map_or_else( - || { - Ok::<_, Error>(power_levels::PowerLevelsEventContent { - ban: 50.into(), - events: BTreeMap::new(), - events_default: 0.into(), - invite: 50.into(), - kick: 50.into(), - redact: 50.into(), - state_default: 0.into(), - users: BTreeMap::new(), - users_default: 0.into(), - notifications: ruma::events::room::power_levels::NotificationPowerLevels { - room: 50.into(), - }, - }) - }, + || Ok::<_, Error>(power_levels::PowerLevelsEventContent::default()), |power_levels| { serde_json::from_value::(power_levels.content()) .map_err(Into::into) @@ -458,7 +443,7 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>) tracing::debug!( "{} ev_type {} usr {}", - event.event_id().to_string(), + event.event_id().as_str(), event_type_power_level, user_level ); @@ -773,7 +758,7 @@ pub fn get_send_level( } lvl } else { - 50 + 50 // default power level } } else { 0 diff --git a/src/lib.rs b/src/lib.rs index de68bac3..2411d42c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,7 +72,7 @@ impl StateResolution { } } - event_auth::auth_check(room_version, &ev, prev_event, auth_events, None) + event_auth::auth_check(room_version, &ev, prev_event, &auth_events, None) } /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph @@ -586,7 +586,7 @@ impl StateResolution { room_version, &event, most_recent_prev_event, - auth_events, + &auth_events, current_third_party, )? { // add event to resolved state map diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 46270ca7..4d9c6fe7 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -10,7 +10,7 @@ use state_res::{ }; mod utils; -use utils::{alice, charlie, event_id, member_content_ban, room_id, to_pdu_event, INITIAL_EVENTS}; +use utils::{alice, charlie, event_id, member_content_ban, to_pdu_event, INITIAL_EVENTS}; #[test] fn test_ban_pass() { @@ -32,8 +32,8 @@ fn test_ban_pass() { ruma::events::EventType::RoomMember, Some(charlie().as_str()), member_content_ban(), - &vec![], - &vec![event_id("IMC")], + &[], + &[event_id("IMC")], ); assert!(valid_membership_change( @@ -67,8 +67,8 @@ fn test_ban_fail() { ruma::events::EventType::RoomMember, Some(alice().as_str()), member_content_ban(), - &vec![], - &vec![event_id("IMC")], + &[], + &[event_id("IMC")], ); assert!(!valid_membership_change( diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 168cf03e..2448e0a9 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -4,7 +4,7 @@ use ruma::{ events::EventType, identifiers::{EventId, RoomVersionId}, }; -use state_res::{is_power_event, Event, StateMap}; +use state_res::{is_power_event, StateMap}; mod utils; use utils::{room_id, TestStore, INITIAL_EVENTS}; From 47b19fdc1536a883534e0a9fbad640219e118d95 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 5 Jan 2021 17:11:30 -0500 Subject: [PATCH 090/130] Remove StateStore trait, caller must collect all events needed --- benches/event_auth_bench.rs | 1 + benches/state_res_bench.rs | 54 +++++++++--- src/event_auth.rs | 6 +- src/lib.rs | 169 +++++++++++++----------------------- src/state_store.rs | 1 + tests/event_sorting.rs | 6 +- tests/res_with_auth_ids.rs | 60 +++---------- tests/state_res.rs | 18 ++-- tests/utils.rs | 29 +++++-- 9 files changed, 155 insertions(+), 189 deletions(-) diff --git a/benches/event_auth_bench.rs b/benches/event_auth_bench.rs index e69de29b..8b137891 100644 --- a/benches/event_auth_bench.rs +++ b/benches/event_auth_bench.rs @@ -0,0 +1 @@ + diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 86d55ff1..b543de02 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -48,13 +48,21 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { let (state_at_bob, state_at_charlie, _) = store.set_up(); b.iter(|| { - let mut ev_map = state_res::EventMap::default(); - let _resolved = match StateResolution::resolve( + let mut ev_map: state_res::EventMap> = store.0.clone(); + let state_sets = vec![state_at_bob.clone(), state_at_charlie.clone()]; + let _ = match StateResolution::resolve::( &room_id(), - &RoomVersionId::Version6, - &[state_at_bob.clone(), state_at_charlie.clone()], + &RoomVersionId::Version2, + &state_sets, + state_sets + .iter() + .map(|map| { + store + .auth_event_ids(&room_id(), &map.values().cloned().collect::>()) + .unwrap() + }) + .collect(), &mut ev_map, - &store, ) { Ok(state) => state, Err(e) => panic!("{}", e), @@ -99,12 +107,20 @@ fn resolve_deeper_event_set(c: &mut Criterion) { .collect::>(); b.iter(|| { - let _resolved = match StateResolution::resolve( + let state_sets = vec![state_set_a.clone(), state_set_b.clone()]; + let _ = match StateResolution::resolve::( &room_id(), - &RoomVersionId::Version6, - &[state_set_a.clone(), state_set_b.clone()], + &RoomVersionId::Version2, + &state_sets, + state_sets + .iter() + .map(|map| { + store + .auth_event_ids(&room_id(), &map.values().cloned().collect::>()) + .unwrap() + }) + .collect(), &mut inner, - &store, ) { Ok(state) => state, Err(_) => panic!("resolution failed during benchmarking"), @@ -530,7 +546,9 @@ pub mod event { fn hashes(&self) -> &EventHash { self.hashes() } - fn signatures(&self) -> BTreeMap, BTreeMap> { + fn signatures( + &self, + ) -> BTreeMap, BTreeMap> { self.signatures() } fn unsigned(&self) -> &BTreeMap { @@ -643,7 +661,10 @@ pub mod event { } impl StateEvent { - pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { + pub fn from_id_value( + id: EventId, + json: serde_json::Value, + ) -> Result { Ok(Self::Full( id, Pdu::RoomV3Pdu(serde_json::from_value(json)?), @@ -671,6 +692,7 @@ pub mod event { EventType::RoomMember => { if let Ok(content) = // TODO fix clone + serde_json::from_value::(event.content.clone()) { if [MembershipState::Leave, MembershipState::Ban] @@ -771,7 +793,9 @@ pub mod event { pub fn prev_event_ids(&self) -> Vec { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV1Pdu(ev) => { + ev.prev_events.iter().map(|(id, _)| id).cloned().collect() + } Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, } @@ -780,7 +804,9 @@ pub mod event { pub fn auth_events(&self) -> Vec { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV1Pdu(ev) => { + ev.auth_events.iter().map(|(id, _)| id).cloned().collect() + } Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, } @@ -860,4 +886,4 @@ pub mod event { } } } -} \ No newline at end of file +} diff --git a/src/event_auth.rs b/src/event_auth.rs index 29c0e703..21b69515 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc}; +use std::{convert::TryFrom, sync::Arc}; use maplit::btreeset; use ruma::{ @@ -67,6 +67,10 @@ pub fn auth_types_for_event( /// * check that the event is being authenticated for the correct room /// * check that the events signatures are valid /// * then there are checks for specific event types +/// +/// The `auth_events` that are passed to this function should be a state snapshot. +/// We need to know if the event passes auth against some state not a recursive collection +/// of auth_events fields. pub fn auth_check( room_version: &RoomVersionId, incoming_event: &Arc, diff --git a/src/lib.rs b/src/lib.rs index 2411d42c..5622e15a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,14 +46,13 @@ impl StateResolution { room_version: &RoomVersionId, incoming_event: Arc, current_state: &StateMap, - event_map: &mut EventMap>, - store: &dyn StateStore, + event_map: &EventMap>, ) -> Result { tracing::info!("Applying a single event, state resolution starting"); let ev = incoming_event; let prev_event = if let Some(id) = ev.prev_events().first() { - store.get_event(room_id, id).ok() + event_map.get(id).map(Arc::clone) } else { None }; @@ -63,9 +62,7 @@ impl StateResolution { event_auth::auth_types_for_event(&ev.kind(), &ev.sender(), ev.state_key(), ev.content()) { if let Some(ev_id) = current_state.get(&key) { - if let Some(event) = - StateResolution::get_or_load_event(room_id, ev_id, event_map, store) - { + if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); } @@ -83,22 +80,21 @@ impl StateResolution { /// * `state_sets` - The incoming state to resolve. Each `StateMap` represents a possible fork /// in the state of a room. /// + /// * `auth_events` - The full recursive set of `auth_events` for each event in the `state_sets`. + /// /// * `event_map` - The `EventMap` acts as a local cache of state, any event that is not found /// in the `event_map` will be fetched from the `StateStore` and cached in the `event_map`. There /// is no state kept from separate `resolve` calls, although this could be a potential optimization /// in the future. /// - /// * `store` - Any type that implements `StateStore` acts as the database. When an event is not - /// found in the `event_map` it will be retrieved from the `store`. - /// /// It is up the the caller to check that the events returned from `StateStore::get_event` are /// events for the correct room (synapse checks that all events are in the right room). pub fn resolve( room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], + auth_events: Vec>, event_map: &mut EventMap>, - store: &dyn StateStore, ) -> Result> { tracing::info!("State resolution starting"); @@ -115,9 +111,9 @@ impl StateResolution { tracing::info!("{} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks - let mut auth_diff = StateResolution::get_auth_chain_diff(room_id, &state_sets, store)?; + let mut auth_diff = StateResolution::get_auth_chain_diff(room_id, &auth_events)?; - tracing::debug!("auth diff size {}", auth_diff.len()); + tracing::debug!("auth diff size {:?}", auth_diff); // add the auth_diff to conflicting now we have a full set of conflicting events auth_diff.extend(conflicting.values().cloned().flatten()); @@ -129,25 +125,6 @@ impl StateResolution { tracing::info!("full conflicted set is {} events", all_conflicted.len()); - // gather missing events for the event_map - let events = store - .get_events( - room_id, - &all_conflicted - .iter() - // we only want the events we don't know about yet - .filter(|id| !event_map.contains_key(id)) - .cloned() - .collect::>(), - ) - .unwrap(); - - // update event_map to include the fetched events - event_map.extend(events.into_iter().map(|ev| (ev.event_id().clone(), ev))); - // at this point our event_map == store there should be no missing events - - tracing::debug!("event map size: {}", event_map.len()); - // we used to check that all events are events from the correct room // this is now a check the caller of `resolve` must make. @@ -168,17 +145,10 @@ impl StateResolution { room_id, &control_events, event_map, - store, &all_conflicted, ); - tracing::debug!( - "SRTD {:?}", - sorted_control_levels - .iter() - .map(ToString::to_string) - .collect::>() - ); + tracing::debug!("SRTD {:?}", sorted_control_levels); // sequentially auth check each control event. let resolved_control = StateResolution::iterative_auth_check( @@ -187,7 +157,6 @@ impl StateResolution { &sorted_control_levels, &clean, event_map, - store, )?; tracing::debug!( @@ -223,13 +192,8 @@ impl StateResolution { tracing::debug!("PL {:?}", power_event); - let sorted_left_events = StateResolution::mainline_sort( - room_id, - &events_to_resolve, - power_event, - event_map, - store, - ); + let sorted_left_events = + StateResolution::mainline_sort(room_id, &events_to_resolve, power_event, event_map); tracing::debug!( "SORTED LEFT {:?}", @@ -245,7 +209,6 @@ impl StateResolution { &sorted_left_events, &resolved_control, // The control events are added to the final resolved state event_map, - store, )?; // add unconflicted state to the resolved state @@ -298,23 +261,34 @@ impl StateResolution { } /// Returns a Vec of deduped EventIds that appear in some chains but not others. - pub fn get_auth_chain_diff( - room_id: &RoomId, - state_sets: &[StateMap], - store: &dyn StateStore, + pub fn get_auth_chain_diff( + _room_id: &RoomId, + auth_event_ids: &[Vec], ) -> Result> { - use itertools::Itertools; + let mut chains = vec![]; - tracing::debug!("calculating auth chain difference"); + for ids in auth_event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = ids.iter().cloned().collect::>(); + chains.push(chain); + } - store.auth_chain_diff( - room_id, - state_sets + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains .iter() - .map(|map| map.values().cloned().collect()) - .dedup() - .collect::>(), - ) + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } } /// Events are sorted from "earliest" to "latest". They are compared using @@ -328,7 +302,6 @@ impl StateResolution { room_id: &RoomId, events_to_sort: &[EventId], event_map: &mut EventMap>, - store: &dyn StateStore, auth_diff: &[EventId], ) -> Vec { tracing::debug!("reverse topological sort of power events"); @@ -336,7 +309,7 @@ impl StateResolution { let mut graph = BTreeMap::new(); for (idx, event_id) in events_to_sort.iter().enumerate() { StateResolution::add_event_and_auth_chain_to_graph( - room_id, &mut graph, event_id, event_map, store, auth_diff, + room_id, &mut graph, event_id, event_map, auth_diff, ); // We yield occasionally when we're working with large data sets to @@ -349,8 +322,7 @@ impl StateResolution { // 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 = - StateResolution::get_power_level_for_sender(room_id, &event_id, event_map, store); + let pl = StateResolution::get_power_level_for_sender(room_id, &event_id, event_map); tracing::info!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -454,11 +426,10 @@ impl StateResolution { room_id: &RoomId, event_id: &EventId, event_map: &mut EventMap>, - store: &dyn StateStore, ) -> i64 { tracing::info!("fetch event ({}) senders power level", event_id.to_string()); - let event = StateResolution::get_or_load_event(room_id, event_id, event_map, store); + let event = StateResolution::get_or_load_event(room_id, event_id, event_map); let mut pl = None; // TODO store.auth_event_ids returns "self" with the event ids is this ok @@ -468,7 +439,7 @@ impl StateResolution { .map(|pdu| pdu.auth_events()) .unwrap_or_default() { - if let Some(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map, store) { + if let Ok(aev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { if is_type_and_key(&aev, EventType::RoomPowerLevels, "") { pl = Some(aev); break; @@ -489,9 +460,9 @@ impl StateResolution { }) .flatten() { - if let Some(ev) = event { + if let Ok(ev) = event { if let Some(user) = content.users.get(&ev.sender()) { - tracing::debug!("found {} at power_level {}", ev.sender().to_string(), user); + tracing::debug!("found {} at power_level {}", ev.sender().as_str(), user); return (*user).into(); } } @@ -517,7 +488,6 @@ impl StateResolution { events_to_check: &[EventId], unconflicted_state: &StateMap, event_map: &mut EventMap>, - store: &dyn StateStore, ) -> Result> { tracing::info!("starting iterative auth check"); @@ -532,14 +502,11 @@ impl StateResolution { let mut resolved_state = unconflicted_state.clone(); for (idx, event_id) in events_to_check.iter().enumerate() { - let event = - StateResolution::get_or_load_event(room_id, event_id, event_map, store).unwrap(); + let event = StateResolution::get_or_load_event(room_id, event_id, event_map)?; let mut auth_events = BTreeMap::new(); for aid in &event.auth_events() { - if let Some(ev) = - StateResolution::get_or_load_event(room_id, &aid, event_map, store) - { + if let Ok(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); @@ -555,8 +522,7 @@ impl StateResolution { event.content(), ) { if let Some(ev_id) = resolved_state.get(&key) { - if let Some(event) = - StateResolution::get_or_load_event(room_id, ev_id, event_map, store) + if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { // TODO synapse checks `rejected_reason` is None here auth_events.insert(key.clone(), event); @@ -569,7 +535,7 @@ impl StateResolution { let most_recent_prev_event = event .prev_events() .iter() - .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store)) + .filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map).ok()) .next_back(); // The key for this is (eventType + a state_key of the signed token not sender) so search @@ -620,7 +586,6 @@ impl StateResolution { to_sort: &[EventId], resolved_power_level: Option<&EventId>, event_map: &mut EventMap>, - store: &dyn StateStore, ) -> Vec { tracing::debug!("mainline sort of events"); @@ -635,12 +600,11 @@ impl StateResolution { while let Some(p) = pl { mainline.push(p.clone()); - let event = StateResolution::get_or_load_event(room_id, &p, event_map, store).unwrap(); + let event = StateResolution::get_or_load_event(room_id, &p, event_map).unwrap(); let auth_events = &event.auth_events(); pl = None; for aid in auth_events { - let ev = - StateResolution::get_or_load_event(room_id, &aid, event_map, store).unwrap(); + let ev = StateResolution::get_or_load_event(room_id, &aid, event_map).unwrap(); if is_type_and_key(&ev, EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; @@ -663,15 +627,12 @@ impl StateResolution { let mut order_map = BTreeMap::new(); for (idx, ev_id) in to_sort.iter().enumerate() { - if let Some(event) = - StateResolution::get_or_load_event(room_id, ev_id, event_map, store) - { + if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { if let Ok(depth) = StateResolution::get_mainline_depth( room_id, Some(event), &mainline_map, event_map, - store, ) { order_map.insert( ev_id, @@ -706,7 +667,6 @@ impl StateResolution { mut event: Option>, mainline_map: &EventMap, event_map: &mut EventMap>, - store: &dyn StateStore, ) -> Result { while let Some(sort_ev) = event { tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); @@ -720,8 +680,7 @@ impl StateResolution { event = None; for aid in auth_events { // dbg!(&aid); - let aev = StateResolution::get_or_load_event(room_id, &aid, event_map, store) - .ok_or_else(|| Error::NotFound("Auth event not found".to_owned()))?; + let aev = StateResolution::get_or_load_event(room_id, &aid, event_map)?; if is_type_and_key(&aev, EventType::RoomPowerLevels, "") { event = Some(aev); break; @@ -737,7 +696,6 @@ impl StateResolution { graph: &mut BTreeMap>, event_id: &EventId, event_map: &mut EventMap>, - store: &dyn StateStore, auth_diff: &[EventId], ) { let mut state = vec![event_id.clone()]; @@ -747,7 +705,7 @@ impl StateResolution { graph.entry(eid.clone()).or_insert_with(Vec::new); // prefer the store to event as the store filters dedups the events // otherwise it seems we can loop forever - for aid in &StateResolution::get_or_load_event(room_id, &eid, event_map, store) + for aid in &StateResolution::get_or_load_event(room_id, &eid, event_map) .unwrap() .auth_events() { @@ -763,27 +721,16 @@ impl StateResolution { } } - // TODO having the event_map as a field of self would allow us to keep - // cached state from `resolve` to `resolve` calls, good idea or not? - /// Uses the `event_map` to return the full PDU or fetches from the `StateStore` implementation - /// if the event_map does not have the PDU. - /// - /// If the PDU is missing from the `event_map` it is added. + /// Uses the `event_map` to return the full PDU or fails. fn get_or_load_event( - room_id: &RoomId, + _room_id: &RoomId, ev_id: &EventId, - event_map: &mut EventMap>, - store: &dyn StateStore, - ) -> Option> { - if let Some(e) = event_map.get(ev_id) { - return Some(Arc::clone(e)); - } - - if let Ok(e) = store.get_event(room_id, ev_id) { - return Some(Arc::clone(event_map.entry(ev_id.clone()).or_insert(e))); - } - - None + event_map: &EventMap>, + ) -> Result> { + event_map.get(ev_id).map_or_else( + || Err(Error::NotFound(format!("EventId: {:?} not found", ev_id))), + |e| Ok(Arc::clone(e)), + ) } } diff --git a/src/state_store.rs b/src/state_store.rs index 50dad0ea..ada65caa 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -4,6 +4,7 @@ use ruma::identifiers::{EventId, RoomId}; use crate::{Event, Result}; +/// TODO: this is only used in testing on this branch now REMOVE pub trait StateStore { /// Return a single event based on the EventId. fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 2448e0a9..78688322 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -7,7 +7,7 @@ use ruma::{ use state_res::{is_power_event, StateMap}; mod utils; -use utils::{room_id, TestStore, INITIAL_EVENTS}; +use utils::{room_id, INITIAL_EVENTS}; fn shuffle(list: &mut [EventId]) { use rand::Rng; @@ -21,7 +21,6 @@ fn shuffle(list: &mut [EventId]) { fn test_event_sort() { let mut events = INITIAL_EVENTS(); - let store = TestStore(events.clone()); let event_map = events .values() @@ -43,7 +42,6 @@ fn test_event_sort() { &room_id(), &power_events, &mut events, - &store, &auth_chain, ); @@ -55,7 +53,6 @@ fn test_event_sort() { &sorted_power_events, &BTreeMap::new(), // unconflicted events &mut events, - &store, ) .expect("iterative auth check failed on resolved events"); @@ -71,7 +68,6 @@ fn test_event_sort() { &events_to_sort, power_level, &mut events, - &store, ); assert_eq!( diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 627750e2..169714e4 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -7,7 +7,7 @@ use ruma::{ identifiers::{EventId, RoomVersionId}, }; use serde_json::json; -use state_res::{StateMap, StateResolution}; +use state_res::{EventMap, StateMap, StateResolution, StateStore}; mod utils; use utils::{ @@ -36,46 +36,6 @@ fn ban_with_auth_chains() { ); } -// Sanity check that the store is able to fetch auth chain and such -#[test] -fn base_with_auth_chains() { - let store = TestStore(INITIAL_EVENTS()); - - let mut ev_map = state_res::EventMap::default(); - let resolved: BTreeMap<_, EventId> = match StateResolution::resolve( - &room_id(), - &RoomVersionId::Version6, - &[], - &mut ev_map, - &store, - ) { - Ok(state) => state, - Err(e) => panic!("{}", e), - }; - - let resolved = resolved - .values() - .cloned() - .chain(INITIAL_EVENTS().values().map(|e| e.event_id().clone())) - .collect::>(); - - let expected = vec![ - "$CREATE:foo", - "$IJR:foo", - "$IPOWER:foo", - "$IMA:foo", - "$IMB:foo", - "$IMC:foo", - "START", - "END", - ]; - for id in expected.iter().map(|i| event_id(i)) { - // make sure our resolved events are equal to the expected list - assert!(resolved.iter().any(|eid| eid == &id), "{}", id) - } - assert_eq!(expected.len(), resolved.len()) -} - #[test] fn ban_with_auth_chains2() { let init = INITIAL_EVENTS(); @@ -111,13 +71,21 @@ fn ban_with_auth_chains2() { .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) .collect::>(); - let mut ev_map = state_res::EventMap::default(); - let resolved: StateMap = match StateResolution::resolve( + let mut ev_map: EventMap> = store.0.clone(); + let state_sets = vec![state_set_a, state_set_b]; + let resolved = match StateResolution::resolve::( &room_id(), - &RoomVersionId::Version6, - &[state_set_a, state_set_b], + &RoomVersionId::Version2, + &state_sets, + state_sets + .iter() + .map(|map| { + store + .auth_event_ids(&room_id(), &map.values().cloned().collect::>()) + .unwrap() + }) + .collect(), &mut ev_map, - &store, ) { Ok(state) => state, Err(e) => panic!("{}", e), diff --git a/tests/state_res.rs b/tests/state_res.rs index f85f7eec..9d72994d 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -6,7 +6,7 @@ use ruma::{ identifiers::{EventId, RoomVersionId}, }; use serde_json::json; -use state_res::{StateMap, StateResolution}; +use state_res::{StateMap, StateResolution, StateStore}; use tracing_subscriber as tracer; mod utils; @@ -265,13 +265,21 @@ fn test_event_map_none() { // build up the DAG let (state_at_bob, state_at_charlie, expected) = store.set_up(); - let mut ev_map = state_res::EventMap::default(); - let resolved = match StateResolution::resolve( + let mut ev_map: state_res::EventMap> = store.0.clone(); + let state_sets = vec![state_at_bob, state_at_charlie]; + let resolved = match StateResolution::resolve::( &room_id(), &RoomVersionId::Version2, - &[state_at_bob, state_at_charlie], + &state_sets, + state_sets + .iter() + .map(|map| { + store + .auth_event_ids(&room_id(), &map.values().cloned().collect::>()) + .unwrap() + }) + .collect(), &mut ev_map, - &store, ) { Ok(state) => state, Err(e) => panic!("{}", e), diff --git a/tests/utils.rs b/tests/utils.rs index bec72356..c3570ea6 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -114,8 +114,15 @@ pub fn do_check( &room_id(), &RoomVersionId::Version6, &state_sets, + state_sets + .iter() + .map(|map| { + store + .auth_event_ids(&room_id(), &map.values().cloned().collect::>()) + .unwrap() + }) + .collect(), &mut event_map, - &store, ); match resolved { Ok(state) => state, @@ -565,7 +572,9 @@ pub mod event { fn hashes(&self) -> &EventHash { self.hashes() } - fn signatures(&self) -> BTreeMap, BTreeMap> { + fn signatures( + &self, + ) -> BTreeMap, BTreeMap> { self.signatures() } fn unsigned(&self) -> &BTreeMap { @@ -678,7 +687,10 @@ pub mod event { } impl StateEvent { - pub fn from_id_value(id: EventId, json: serde_json::Value) -> Result { + pub fn from_id_value( + id: EventId, + json: serde_json::Value, + ) -> Result { Ok(Self::Full( id, Pdu::RoomV3Pdu(serde_json::from_value(json)?), @@ -806,7 +818,9 @@ pub mod event { pub fn prev_event_ids(&self) -> Vec { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV1Pdu(ev) => { + ev.prev_events.iter().map(|(id, _)| id).cloned().collect() + } Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, } @@ -815,7 +829,9 @@ pub mod event { pub fn auth_events(&self) -> Vec { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV1Pdu(ev) => { + ev.auth_events.iter().map(|(id, _)| id).cloned().collect() + } Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, } @@ -936,5 +952,4 @@ pub mod event { ) } } - -} \ No newline at end of file +} From 37bff47a8e376b22b8116c19e820446f28860bfb Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 12 Jan 2021 08:29:39 -0500 Subject: [PATCH 091/130] Add clearer docs to auth_check --- src/event_auth.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event_auth.rs b/src/event_auth.rs index 21b69515..bc87ec6f 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -71,6 +71,9 @@ pub fn auth_types_for_event( /// The `auth_events` that are passed to this function should be a state snapshot. /// We need to know if the event passes auth against some state not a recursive collection /// of auth_events fields. +/// +/// ## Returns +/// This returns an `Error` only when serialization fails or some other fatal outcome. pub fn auth_check( room_version: &RoomVersionId, incoming_event: &Arc, From d7144e63b71815464473c5156f0f58e879b86d45 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 13 Jan 2021 15:38:27 -0500 Subject: [PATCH 092/130] Update resolve methods docs for event_map --- src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5622e15a..27a8b4c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,12 +83,7 @@ impl StateResolution { /// * `auth_events` - The full recursive set of `auth_events` for each event in the `state_sets`. /// /// * `event_map` - The `EventMap` acts as a local cache of state, any event that is not found - /// in the `event_map` will be fetched from the `StateStore` and cached in the `event_map`. There - /// is no state kept from separate `resolve` calls, although this could be a potential optimization - /// in the future. - /// - /// It is up the the caller to check that the events returned from `StateStore::get_event` are - /// events for the correct room (synapse checks that all events are in the right room). + /// in the `event_map` will cause an unrecoverable `Error` in `resolve`. pub fn resolve( room_id: &RoomId, room_version: &RoomVersionId, From 4cb73531615e903176a5b8e370b4392137bfb8ac Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 14 Jan 2021 20:23:46 -0500 Subject: [PATCH 093/130] Remove outdated TODOs --- src/event_auth.rs | 1 - src/lib.rs | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index bc87ec6f..5b8e5e76 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -43,7 +43,6 @@ pub fn auth_types_for_event( } } - // TODO what when we don't find a state_key let key = (EventType::RoomMember, state_key); if !auth_types.contains(&key) { auth_types.push(key) diff --git a/src/lib.rs b/src/lib.rs index 27a8b4c2..7bd58549 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -502,7 +502,6 @@ impl StateResolution { let mut auth_events = BTreeMap::new(); for aid in &event.auth_events() { if let Ok(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { - // TODO what to do when no state_key is found ?? // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); } else { @@ -747,14 +746,9 @@ pub fn is_power_event(event: &Arc) -> bool { event.state_key() == Some("".into()) } EventType::RoomMember => { - if let Ok(content) = - // TODO fix clone - serde_json::from_value::(event.content()) - { + if let Ok(content) = serde_json::from_value::(event.content()) { if [MembershipState::Leave, MembershipState::Ban].contains(&content.membership) { - return event.sender().as_str() - // TODO is None here a failure - != event.state_key().as_deref().unwrap_or("NOT A STATE KEY"); + return Some(event.sender().as_str()) != event.state_key().as_deref(); } } From 8265247f7a2df54e85b971f660c7d002deb216ae Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 18 Jan 2021 19:13:07 -0500 Subject: [PATCH 094/130] Update ruma to latest ruma-signatures got an update --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e196720a..148ddfe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ thiserror = "1.0.22" git = "https://github.com/ruma/ruma" # branch = "server-pdu" # path = "../__forks__/ruma/ruma" -rev = "210b6dd823ba89c5a44c3c9d913d377c4b54c896" +rev = "0635b407290abf5f34d726e1e690c92c07c738e5" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] [features] From 594082cbdae8445443754ea271fb3a76013535de Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sun, 24 Jan 2021 20:56:04 -0500 Subject: [PATCH 095/130] Update readme to how the library is set up now --- README.md | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 91f28152..a547805d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ ### Matrix state resolution in rust! ```rust -/// StateMap is just a wrapper/deserialize target for a PDU. -struct StateEvent { - content: serde_json::Value, - origin_server_ts: SystemTime, - sender: UserId, - // ... and so on +/// Abstraction of a PDU so users can have their own PDU types. +pub trait Event { + /// The `EventId` of this event. + fn event_id(&self) -> &EventId; + /// The `RoomId` of this event. + fn room_id(&self) -> &RoomId; + /// The `UserId` of this event. + fn sender(&self) -> &UserId; + // and so on... } /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, String), T>; +pub type StateMap = BTreeMap<(EventType, Option), T>; /// A mapping of `EventId` to `T`, usually a `StateEvent`. pub type EventMap = BTreeMap; @@ -18,32 +21,22 @@ pub type EventMap = BTreeMap; struct StateResolution { // For now the StateResolution struct is empty. If "caching" `event_map` // between `resolve` calls ends up being more efficient (probably not, as this would eat memory) - // it may have an `event_map` field. The `event_map` is all the event's - // `StateResolution` has to know about in order to resolve state. + // it may have an `event_map` field. The `event_map` is all the events + // `StateResolution` has to know about to resolve state. } impl StateResolution { /// The point of this all, resolve the possibly conflicting sets of events. - pub fn resolve( + pub fn resolve( room_id: &RoomId, room_version: &RoomVersionId, state_sets: &[StateMap], - event_map: Option>, - store: &dyn StateStore, - ) -> Result, Error>; + auth_events: Vec>, + event_map: &mut EventMap>, + ) -> Result> {; } -// The tricky part, making a good abstraction... -trait StateStore { - /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result; - - // There are 3 methods that have default implementations `get_events`, - // `auth_event_ids` and `auth_chain_diff`. Each could be overridden if - // the user has an optimization with their database of choice. -} - ``` From 3c27c8b4485bce3474eebf7ff4e5f1b493895e42 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 29 Jan 2021 10:39:14 -0500 Subject: [PATCH 096/130] Update ruma and itertools --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 148ddfe6..ff091cf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" repository = "https://github.com/ruma/state-res" [dependencies] -itertools = "0.9.0" +itertools = "0.10.0" serde = { version = "1.0.118", features = ["derive"] } serde_json = "1.0.60" tracing = "0.1.22" @@ -23,7 +23,7 @@ thiserror = "1.0.22" git = "https://github.com/ruma/ruma" # branch = "server-pdu" # path = "../__forks__/ruma/ruma" -rev = "0635b407290abf5f34d726e1e690c92c07c738e5" +rev = "bba442580d6cd7ed990b2b63387eed2238cbadc8" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] [features] From 791c66d73cf064d09db0cdf767d5fef43a343425 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 29 Jan 2021 11:18:01 -0500 Subject: [PATCH 097/130] Replace tracing crate with log (conduit uses log) --- Cargo.toml | 2 +- src/event_auth.rs | 74 +++++++++++++++++++------------------- src/lib.rs | 54 ++++++++++++++-------------- tests/res_with_auth_ids.rs | 2 +- tests/utils.rs | 2 +- 5 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff091cf2..23c7ba51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ repository = "https://github.com/ruma/state-res" itertools = "0.10.0" serde = { version = "1.0.118", features = ["derive"] } serde_json = "1.0.60" -tracing = "0.1.22" maplit = "1.0.2" thiserror = "1.0.22" +log = "0.4.11" [dependencies.ruma] git = "https://github.com/ruma/ruma" diff --git a/src/event_auth.rs b/src/event_auth.rs index 5b8e5e76..6c8ed951 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -80,7 +80,7 @@ pub fn auth_check( auth_events: &StateMap>, current_third_party_invite: Option>, ) -> Result { - tracing::info!("auth_check beginning for {}", incoming_event.kind()); + log::info!("auth_check beginning for {}", incoming_event.kind()); // [synapse] check that all the events are in the same room as `incoming_event` @@ -94,17 +94,17 @@ pub fn auth_check( // // 1. If type is m.room.create: if incoming_event.kind() == EventType::RoomCreate { - tracing::info!("start m.room.create check"); + log::info!("start m.room.create check"); // If it has any previous events, reject if !incoming_event.prev_events().is_empty() { - tracing::warn!("the room creation event had previous events"); + log::warn!("the room creation event had previous events"); return Ok(false); } // If the domain of the room_id does not match the domain of the sender, reject if incoming_event.room_id().server_name() != incoming_event.sender().server_name() { - tracing::warn!("creation events server does not match sender"); + log::warn!("creation events server does not match sender"); return Ok(false); // creation events room id does not match senders } @@ -119,17 +119,17 @@ pub fn auth_check( ) .is_err() { - tracing::warn!("invalid room version found in m.room.create event"); + log::warn!("invalid room version found in m.room.create event"); return Ok(false); } // If content has no creator field, reject if incoming_event.content().get("creator").is_none() { - tracing::warn!("no creator field found in room create content"); + log::warn!("no creator field found in room create content"); return Ok(false); } - tracing::info!("m.room.create event was allowed"); + log::info!("m.room.create event was allowed"); return Ok(true); } @@ -149,7 +149,7 @@ pub fn auth_check( for ev_key in auth_events.keys() { // (b) if !expected_auth.contains(ev_key) { - tracing::warn!("auth_events contained invalid auth event"); + log::warn!("auth_events contained invalid auth event"); return Ok(false); } } @@ -160,7 +160,7 @@ pub fn auth_check( .get(&(EventType::RoomCreate, Some("".into()))) .is_none() { - tracing::warn!("no m.room.create event in auth chain"); + log::warn!("no m.room.create event in auth chain"); return Ok(false); } @@ -169,32 +169,32 @@ pub fn auth_check( // 4. if type is m.room.aliases if incoming_event.kind() == EventType::RoomAliases { - tracing::info!("starting m.room.aliases check"); + log::info!("starting m.room.aliases check"); // [synapse] adds `&& room_version` "special case aliases auth" // [synapse] // if event.state_key.unwrap().is_empty() { - // tracing::warn!("state_key must be non-empty"); + // log::warn!("state_key must be non-empty"); // return Ok(false); // and be non-empty state_key (point to a user_id) // } // If sender's domain doesn't matches state_key, reject if incoming_event.state_key() != Some(incoming_event.sender().server_name().to_string()) { - tracing::warn!("state_key does not match sender"); + log::warn!("state_key does not match sender"); return Ok(false); } - tracing::info!("m.room.aliases event was allowed"); + log::info!("m.room.aliases event was allowed"); return Ok(true); } if incoming_event.kind() == EventType::RoomMember { - tracing::info!("starting m.room.member check"); + log::info!("starting m.room.member check"); if serde_json::from_value::(incoming_event.content()) .is_err() { - tracing::warn!("no membership filed found for m.room.member event content"); + log::warn!("no membership filed found for m.room.member event content"); return Ok(false); } @@ -209,7 +209,7 @@ pub fn auth_check( return Ok(false); } - tracing::info!("m.room.member event was allowed"); + log::info!("m.room.member event was allowed"); return Ok(true); } @@ -217,11 +217,11 @@ pub fn auth_check( match check_event_sender_in_room(&incoming_event.sender(), &auth_events) { Some(true) => {} // sender in room Some(false) => { - tracing::warn!("sender's membership is not join"); + log::warn!("sender's membership is not join"); return Ok(false); } None => { - tracing::warn!("sender not found in room"); + log::warn!("sender not found in room"); return Ok(false); } } @@ -231,32 +231,32 @@ pub fn auth_check( if incoming_event.kind() == EventType::RoomThirdPartyInvite && !can_send_invite(incoming_event, &auth_events)? { - tracing::warn!("sender's cannot send invites in this room"); + log::warn!("sender's cannot send invites in this room"); return Ok(false); } // If the event type's required power level is greater than the sender's power level, reject // If the event has a state_key that starts with an @ and does not match the sender, reject. if !can_send_event(&incoming_event, &auth_events) { - tracing::warn!("user cannot send event"); + log::warn!("user cannot send event"); return Ok(false); } if incoming_event.kind() == EventType::RoomPowerLevels { - tracing::info!("starting m.room.power_levels check"); + log::info!("starting m.room.power_levels check"); if let Some(required_pwr_lvl) = check_power_levels(room_version, &incoming_event, &auth_events) { if !required_pwr_lvl { - tracing::warn!("power level was not allowed"); + log::warn!("power level was not allowed"); return Ok(false); } } else { - tracing::warn!("power level was not allowed"); + log::warn!("power level was not allowed"); return Ok(false); } - tracing::info!("power levels event allowed"); + log::info!("power levels event allowed"); } if incoming_event.kind() == EventType::RoomRedaction @@ -265,7 +265,7 @@ pub fn auth_check( return Ok(false); } - tracing::info!("allowing event passed all checks"); + log::info!("allowing event passed all checks"); Ok(true) } @@ -447,7 +447,7 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>) let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple); let user_level = get_user_power_level(&event.sender(), auth_events); - tracing::debug!( + log::debug!( "{} ev_type {} usr {}", event.event_id().as_str(), event_type_power_level, @@ -496,7 +496,7 @@ pub fn check_power_levels( .unwrap(); // validation of users is done in Ruma, synapse for loops validating user_ids and integers here - tracing::info!("validation of power event finished"); + log::info!("validation of power event finished"); let user_level = get_user_power_level(&power_event.sender(), auth_events); @@ -508,7 +508,7 @@ pub fn check_power_levels( user_levels_to_check.insert(user); } - tracing::debug!("users to check {:?}", user_levels_to_check); + log::debug!("users to check {:?}", user_levels_to_check); let mut event_levels_to_check = btreeset![]; let old_list = ¤t_content.events; @@ -518,7 +518,7 @@ pub fn check_power_levels( event_levels_to_check.insert(ev_id); } - tracing::debug!("events to check {:?}", event_levels_to_check); + log::debug!("events to check {:?}", event_levels_to_check); // [synapse] validate MSC2209 depending on room version check "notifications". // if RoomVersion::new(room_version).limit_notifications_power_levels { @@ -528,7 +528,7 @@ pub fn check_power_levels( // 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 { - // tracing::warn!("m.room.power_level cannot add ops > than own"); + // log::warn!("m.room.power_level cannot add ops > than own"); // return Some(false); // cannot add ops greater than own // } // } @@ -549,7 +549,7 @@ pub fn check_power_levels( // If the current value is equal to the sender's current power level, reject if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) { - tracing::warn!("m.room.power_level cannot remove ops == to own"); + log::warn!("m.room.power_level cannot remove ops == to own"); return Some(false); // cannot remove ops level == to own } @@ -558,7 +558,7 @@ pub fn check_power_levels( 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 { - tracing::warn!("m.room.power_level failed to add ops > than own"); + log::warn!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -576,7 +576,7 @@ pub fn check_power_levels( 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 { - tracing::warn!("m.room.power_level failed to add ops > than own"); + log::warn!("m.room.power_level failed to add ops > than own"); return Some(false); // cannot add ops greater than own } } @@ -598,7 +598,7 @@ pub fn check_power_levels( let new_level_too_big = new_lvl > user_level; if old_level_too_big || new_level_too_big { - tracing::warn!("cannot add ops > than own"); + log::warn!("cannot add ops > than own"); return Some(false); } } @@ -628,7 +628,7 @@ pub fn check_redaction( let redact_level = get_named_level(auth_events, "redact", 50); if user_level >= redact_level { - tracing::info!("redaction allowed via power levels"); + log::info!("redaction allowed via power levels"); return Ok(true); } @@ -648,7 +648,7 @@ pub fn check_redaction( .as_ref() .and_then(|id| id.server_name()) { - tracing::info!("redaction event allowed via room version 1 rules"); + log::info!("redaction event allowed via room version 1 rules"); return Ok(true); } } @@ -746,7 +746,7 @@ pub fn get_send_level( state_key: Option, power_lvl: Option<&Arc>, ) -> i64 { - tracing::debug!("{:?} {:?}", e_type, state_key); + log::debug!("{:?} {:?}", e_type, state_key); if let Some(ple) = power_lvl { if let Ok(content) = serde_json::from_value::(ple.content()) diff --git a/src/lib.rs b/src/lib.rs index 7bd58549..99080d75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ impl StateResolution { current_state: &StateMap, event_map: &EventMap>, ) -> Result { - tracing::info!("Applying a single event, state resolution starting"); + log::info!("Applying a single event, state resolution starting"); let ev = incoming_event; let prev_event = if let Some(id) = ev.prev_events().first() { @@ -91,24 +91,24 @@ impl StateResolution { auth_events: Vec>, event_map: &mut EventMap>, ) -> Result> { - tracing::info!("State resolution starting"); + log::info!("State resolution starting"); // split non-conflicting and conflicting state let (clean, conflicting) = StateResolution::separate(&state_sets); - tracing::info!("non conflicting {:?}", clean.len()); + log::info!("non conflicting {:?}", clean.len()); if conflicting.is_empty() { - tracing::info!("no conflicting state found"); + log::info!("no conflicting state found"); return Ok(clean); } - tracing::info!("{} conflicting events", conflicting.len()); + log::info!("{} conflicting events", conflicting.len()); // the set of auth events that are not common across server forks let mut auth_diff = StateResolution::get_auth_chain_diff(room_id, &auth_events)?; - tracing::debug!("auth diff size {:?}", auth_diff); + log::debug!("auth diff size {:?}", auth_diff); // add the auth_diff to conflicting now we have a full set of conflicting events auth_diff.extend(conflicting.values().cloned().flatten()); @@ -118,7 +118,7 @@ impl StateResolution { .into_iter() .collect::>(); - tracing::info!("full conflicted set is {} events", all_conflicted.len()); + log::info!("full conflicted set is {} events", all_conflicted.len()); // we used to check that all events are events from the correct room // this is now a check the caller of `resolve` must make. @@ -143,7 +143,7 @@ impl StateResolution { &all_conflicted, ); - tracing::debug!("SRTD {:?}", sorted_control_levels); + log::debug!("SRTD {:?}", sorted_control_levels); // sequentially auth check each control event. let resolved_control = StateResolution::iterative_auth_check( @@ -154,7 +154,7 @@ impl StateResolution { event_map, )?; - tracing::debug!( + log::debug!( "AUTHED {:?}", resolved_control .iter() @@ -174,7 +174,7 @@ impl StateResolution { .cloned() .collect::>(); - tracing::debug!( + log::debug!( "LEFT {:?}", events_to_resolve .iter() @@ -185,12 +185,12 @@ impl StateResolution { // This "epochs" power level event let power_event = resolved_control.get(&(EventType::RoomPowerLevels, Some("".into()))); - tracing::debug!("PL {:?}", power_event); + log::debug!("PL {:?}", power_event); let sorted_left_events = StateResolution::mainline_sort(room_id, &events_to_resolve, power_event, event_map); - tracing::debug!( + log::debug!( "SORTED LEFT {:?}", sorted_left_events .iter() @@ -223,7 +223,7 @@ impl StateResolution { ) -> (StateMap, StateMap>) { use itertools::Itertools; - tracing::info!( + log::info!( "seperating {} sets of events into conflicted/unconflicted", state_sets.len() ); @@ -299,7 +299,7 @@ impl StateResolution { event_map: &mut EventMap>, auth_diff: &[EventId], ) -> Vec { - tracing::debug!("reverse topological sort of power events"); + log::debug!("reverse topological sort of power events"); let mut graph = BTreeMap::new(); for (idx, event_id) in events_to_sort.iter().enumerate() { @@ -318,7 +318,7 @@ impl StateResolution { let mut event_to_pl = BTreeMap::new(); for (idx, event_id) in graph.keys().enumerate() { let pl = StateResolution::get_power_level_for_sender(room_id, &event_id, event_map); - tracing::info!("{} power level {}", event_id.to_string(), pl); + log::info!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -330,11 +330,11 @@ impl StateResolution { } StateResolution::lexicographical_topological_sort(&graph, |event_id| { - // tracing::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); + // log::debug!("{:?}", event_map.get(event_id).unwrap().origin_server_ts()); let ev = event_map.get(event_id).unwrap(); let pl = event_to_pl.get(event_id).unwrap(); - tracing::debug!("{:?}", (-*pl, ev.origin_server_ts(), &ev.event_id())); + log::debug!("{:?}", (-*pl, ev.origin_server_ts(), &ev.event_id())); // This return value is the key used for sorting events, // events are then sorted by power level, time, @@ -353,7 +353,7 @@ impl StateResolution { where F: Fn(&EventId) -> (i64, SystemTime, EventId), { - tracing::info!("starting lexicographical topological sort"); + log::info!("starting lexicographical topological sort"); // NOTE: an event that has no incoming edges happened most recently, // and an event that has no outgoing edges happened least recently. @@ -422,7 +422,7 @@ impl StateResolution { event_id: &EventId, event_map: &mut EventMap>, ) -> i64 { - tracing::info!("fetch event ({}) senders power level", event_id.to_string()); + log::info!("fetch event ({}) senders power level", event_id.to_string()); let event = StateResolution::get_or_load_event(room_id, event_id, event_map); let mut pl = None; @@ -457,7 +457,7 @@ impl StateResolution { { if let Ok(ev) = event { if let Some(user) = content.users.get(&ev.sender()) { - tracing::debug!("found {} at power_level {}", ev.sender().as_str(), user); + log::debug!("found {} at power_level {}", ev.sender().as_str(), user); return (*user).into(); } } @@ -484,9 +484,9 @@ impl StateResolution { unconflicted_state: &StateMap, event_map: &mut EventMap>, ) -> Result> { - tracing::info!("starting iterative auth check"); + log::info!("starting iterative auth check"); - tracing::debug!( + log::debug!( "performing auth checks on {:?}", events_to_check .iter() @@ -505,7 +505,7 @@ impl StateResolution { // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? auth_events.insert((ev.kind(), ev.state_key()), ev); } else { - tracing::warn!("auth event id for {} is missing {}", aid, event_id); + log::warn!("auth event id for {} is missing {}", aid, event_id); } } @@ -524,7 +524,7 @@ impl StateResolution { } } - tracing::debug!("event to check {:?}", event.event_id().as_str()); + log::debug!("event to check {:?}", event.event_id().as_str()); let most_recent_prev_event = event .prev_events() @@ -553,7 +553,7 @@ impl StateResolution { resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); } else { // synapse passes here on AuthError. We do not add this event to resolved_state. - tracing::warn!( + log::warn!( "event {} failed the authentication check", event_id.to_string() ); @@ -581,7 +581,7 @@ impl StateResolution { resolved_power_level: Option<&EventId>, event_map: &mut EventMap>, ) -> Vec { - tracing::debug!("mainline sort of events"); + log::debug!("mainline sort of events"); // There are no EventId's to sort, bail. if to_sort.is_empty() { @@ -663,7 +663,7 @@ impl StateResolution { event_map: &mut EventMap>, ) -> Result { while let Some(sort_ev) = event { - tracing::debug!("mainline event_id {}", sort_ev.event_id().to_string()); + log::debug!("mainline event_id {}", sort_ev.event_id().to_string()); let id = &sort_ev.event_id(); if let Some(depth) = mainline_map.get(&id) { return Ok(*depth); diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 169714e4..de99ac4e 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -91,7 +91,7 @@ fn ban_with_auth_chains2() { Err(e) => panic!("{}", e), }; - tracing::debug!( + log::debug!( "{:#?}", resolved .iter() diff --git a/tests/utils.rs b/tests/utils.rs index c3570ea6..7423eb0e 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -99,7 +99,7 @@ pub fn do_check( .cloned() .collect::>(); - tracing::info!( + log::info!( "{:#?}", state_sets .iter() From d34a78c5b66de419862d9e592bde8e0007111ebd Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 8 Feb 2021 09:38:04 -0500 Subject: [PATCH 098/130] Update ruma to allow optional state_key in InitialStateEvent --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 23c7ba51..a300bb1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ log = "0.4.11" git = "https://github.com/ruma/ruma" # branch = "server-pdu" # path = "../__forks__/ruma/ruma" -rev = "bba442580d6cd7ed990b2b63387eed2238cbadc8" +rev = "0a10afe6dacc2b7a50a8002c953d10b7fb4e37bc" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] [features] From da5c74acc3ad68aa57309f6f18b6ca44de29cad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Thu, 18 Mar 2021 18:38:40 +0100 Subject: [PATCH 099/130] fix: event required power levels --- src/event_auth.rs | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 6c8ed951..e3b6df2e 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -747,28 +747,21 @@ pub fn get_send_level( power_lvl: Option<&Arc>, ) -> i64 { log::debug!("{:?} {:?}", e_type, state_key); - if let Some(ple) = power_lvl { - if let Ok(content) = + power_lvl + .and_then(|ple| { serde_json::from_value::(ple.content()) - { - let mut lvl: i64 = content - .events - .get(&e_type) - .cloned() - .unwrap_or_else(|| ruma::int!(50)) - .into(); - let state_def: i64 = content.state_default.into(); - let event_def: i64 = content.events_default.into(); - if (state_key.is_some() && state_def > lvl) || event_def > lvl { - lvl = event_def; - } - lvl - } else { - 50 // default power level - } - } else { - 0 - } + .map(|content| { + content.events.get(&e_type).cloned().unwrap_or_else(|| { + if state_key.is_some() { + content.state_default + } else { + content.events_default + } + }) + }).ok() + }) + .map(|int| i64::from(int)) + .unwrap_or_else(|| if state_key.is_some() { 50 } else { 0 }) } /// Check user can send invite. From 34cd1cb4dcdd5fb84b5df9e48e63b2e4669a2488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Thu, 18 Mar 2021 19:20:27 +0100 Subject: [PATCH 100/130] bump ruma --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a300bb1b..be90d6bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,9 @@ log = "0.4.11" [dependencies.ruma] git = "https://github.com/ruma/ruma" -# branch = "server-pdu" -# path = "../__forks__/ruma/ruma" -rev = "0a10afe6dacc2b7a50a8002c953d10b7fb4e37bc" +#branch = "server-pdu" +#path = "../ruma/ruma" +rev = "f196f5b6f164973d6b343af31ab4e0457f743675" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] [features] From 1621a491a9e867a1ad4dff9f2f92b0c1e2d44aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Thu, 25 Mar 2021 23:18:17 +0100 Subject: [PATCH 101/130] improvement: refactor code and fix a few auth rules --- benches/state_res_bench.rs | 10 ++-- src/event_auth.rs | 117 ++++++++++++++++++------------------- src/lib.rs | 15 +++-- tests/event_auth.rs | 4 +- tests/event_sorting.rs | 2 +- tests/utils.rs | 20 +++---- 6 files changed, 84 insertions(+), 84 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index b543de02..dc8696c6 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -90,7 +90,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) + .map(|ev| ((ev.kind(), ev.state_key().unwrap()), ev.event_id().clone())) .collect::>(); let state_set_b = [ @@ -103,7 +103,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { inner.get(&event_id("PA")).unwrap(), ] .iter() - .map(|ev| ((ev.kind(), ev.state_key()), ev.event_id().clone())) + .map(|ev| ((ev.kind(), ev.state_key().unwrap()), ev.event_id().clone())) .collect::>(); b.iter(|| { @@ -220,12 +220,12 @@ impl TestStore { let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) + .map(|e| ((e.kind(), e.state_key().unwrap()), e.event_id().clone())) .collect::>(); let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) + .map(|e| ((e.kind(), e.state_key().unwrap()), e.event_id().clone())) .collect::>(); let expected = [ @@ -236,7 +236,7 @@ impl TestStore { &charlie_mem, ] .iter() - .map(|e| ((e.kind(), e.state_key()), e.event_id().clone())) + .map(|e| ((e.kind(), e.state_key().unwrap()), e.event_id().clone())) .collect::>(); (state_at_bob, state_at_charlie, expected) diff --git a/src/event_auth.rs b/src/event_auth.rs index e3b6df2e..4f2d95bf 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -23,36 +23,39 @@ pub fn auth_types_for_event( sender: &UserId, state_key: Option, content: serde_json::Value, -) -> Vec<(EventType, Option)> { +) -> Vec<(EventType, String)> { if kind == &EventType::RoomCreate { return vec![]; } let mut auth_types = vec![ - (EventType::RoomPowerLevels, Some("".to_string())), - (EventType::RoomMember, Some(sender.to_string())), - (EventType::RoomCreate, Some("".to_string())), + (EventType::RoomPowerLevels, "".to_string()), + (EventType::RoomMember, sender.to_string()), + (EventType::RoomCreate, "".to_string()), ]; if kind == &EventType::RoomMember { - if let Ok(content) = serde_json::from_value::(content) { - if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { - let key = (EventType::RoomJoinRules, Some("".into())); + if let Some(state_key) = state_key { + if let Ok(content) = serde_json::from_value::(content) + { + if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { + let key = (EventType::RoomJoinRules, "".to_string()); + if !auth_types.contains(&key) { + auth_types.push(key) + } + } + + let key = (EventType::RoomMember, state_key); if !auth_types.contains(&key) { auth_types.push(key) } - } - let key = (EventType::RoomMember, state_key); - if !auth_types.contains(&key) { - auth_types.push(key) - } - - if content.membership == MembershipState::Invite { - if let Some(t_id) = content.third_party_invite { - let key = (EventType::RoomThirdPartyInvite, Some(t_id.signed.token)); - if !auth_types.contains(&key) { - auth_types.push(key) + if content.membership == MembershipState::Invite { + if let Some(t_id) = content.third_party_invite { + let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); + if !auth_types.contains(&key) { + auth_types.push(key) + } } } } @@ -157,7 +160,7 @@ pub fn auth_check( // 3. If event does not have m.room.create in auth_events reject if auth_events - .get(&(EventType::RoomCreate, Some("".into()))) + .get(&(EventType::RoomCreate, "".to_string())) .is_none() { log::warn!("no m.room.create event in auth chain"); @@ -172,12 +175,6 @@ pub fn auth_check( log::info!("starting m.room.aliases check"); // [synapse] adds `&& room_version` "special case aliases auth" - // [synapse] - // if event.state_key.unwrap().is_empty() { - // log::warn!("state_key must be non-empty"); - // return Ok(false); // and be non-empty state_key (point to a user_id) - // } - // If sender's domain doesn't matches state_key, reject if incoming_event.state_key() != Some(incoming_event.sender().server_name().to_string()) { log::warn!("state_key does not match sender"); @@ -190,6 +187,13 @@ pub fn auth_check( if incoming_event.kind() == EventType::RoomMember { log::info!("starting m.room.member check"); + let state_key = match incoming_event.state_key() { + None => { + log::warn!("no statekey in member event"); + return Ok(false); + } + Some(s) => s, + }; if serde_json::from_value::(incoming_event.content()) .is_err() @@ -199,7 +203,7 @@ pub fn auth_check( } if !valid_membership_change( - incoming_event.state_key().as_deref(), + &state_key, incoming_event.sender(), incoming_event.content(), prev_event, @@ -279,19 +283,13 @@ pub fn auth_check( /// this is generated by calling `auth_types_for_event` with the membership event and /// the current State. pub fn valid_membership_change( - user_state_key: Option<&str>, + state_key: &str, user_sender: &UserId, content: serde_json::Value, prev_event: Option>, current_third_party_invite: Option>, auth_events: &StateMap>, ) -> Result { - let state_key = if let Some(s) = user_state_key { - s - } else { - return Err(Error::InvalidPdu("State event requires state_key".into())); - }; - let content = serde_json::from_value::(content)?; let target_membership = content.membership; @@ -299,7 +297,7 @@ pub fn valid_membership_change( let target_user_id = UserId::try_from(state_key).map_err(|e| Error::ConversionError(format!("{}", e)))?; - let key = (EventType::RoomMember, Some(user_sender.to_string())); + let key = (EventType::RoomMember, user_sender.to_string()); let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { @@ -309,7 +307,7 @@ pub fn valid_membership_change( ) })?; - let key = (EventType::RoomMember, Some(target_user_id.to_string())); + let key = (EventType::RoomMember, target_user_id.to_string()); let current = auth_events.get(&key); let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { @@ -319,7 +317,7 @@ pub fn valid_membership_change( ) })?; - let key = (EventType::RoomPowerLevels, Some("".into())); + let key = (EventType::RoomPowerLevels, "".into()); let power_levels = auth_events.get(&key).map_or_else( || Ok::<_, Error>(power_levels::PowerLevelsEventContent::default()), |power_levels| { @@ -351,7 +349,7 @@ pub fn valid_membership_change( Some, ); - let key = (EventType::RoomJoinRules, Some("".into())); + let key = (EventType::RoomJoinRules, "".into()); let join_rules_event = auth_events.get(&key); let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { @@ -430,7 +428,7 @@ pub fn check_event_sender_in_room( sender: &UserId, auth_events: &StateMap>, ) -> Option { - let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?; + let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; Some( serde_json::from_value::(mem.content()) .ok()? @@ -442,7 +440,7 @@ pub fn check_event_sender_in_room( /// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// have the correct userId as it's state_key if it's not the "" state_key. pub fn can_send_event(event: &Arc, auth_events: &StateMap>) -> bool { - let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); + let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple); let user_level = get_user_power_level(&event.sender(), auth_events); @@ -476,7 +474,10 @@ pub fn check_power_levels( power_event: &Arc, auth_events: &StateMap>, ) -> Option { - let key = (power_event.kind(), power_event.state_key()); + let power_event_state_key = power_event + .state_key() + .expect("power events have state keys"); + let key = (power_event.kind(), power_event_state_key); let current_state = if let Some(current_state) = auth_events.get(&key) { current_state } else { @@ -620,7 +621,7 @@ fn get_deserialize_levels( /// Does the event redacting come from a user with enough power to redact the given event. pub fn check_redaction( - room_version: &RoomVersionId, + _room_version: &RoomVersionId, redaction_event: &Arc, auth_events: &StateMap>, ) -> Result { @@ -639,18 +640,15 @@ pub fn check_redaction( // Servers should only apply redaction's to events where the sender's domains match, // or the sender of the redaction has the appropriate permissions per the power levels. - // version 1 check - if let RoomVersionId::Version1 = room_version { - // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow - if redaction_event.event_id().server_name() - == redaction_event - .redacts() - .as_ref() - .and_then(|id| id.server_name()) - { - log::info!("redaction event allowed via room version 1 rules"); - return Ok(true); - } + // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow + if redaction_event.event_id().server_name() + == redaction_event + .redacts() + .as_ref() + .and_then(|id| id.server_name()) + { + log::info!("redaction event allowed via room version 1 rules"); + return Ok(true); } Ok(false) @@ -675,7 +673,7 @@ pub fn check_membership(member_event: Option>, state: Membershi /// Can this room federate based on its m.room.create event. pub fn can_federate(auth_events: &StateMap>) -> bool { - let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); + 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 == "true" @@ -690,7 +688,7 @@ pub fn can_federate(auth_events: &StateMap>) -> bool { /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// or return `default` if no power level event is found or zero if no field matches `name`. pub fn get_named_level(auth_events: &StateMap>, name: &str, default: i64) -> i64 { - let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); + 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) { @@ -706,7 +704,7 @@ pub fn get_named_level(auth_events: &StateMap>, name: &str, def /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// object. pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { - if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))) { + if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { if let Ok(content) = serde_json::from_value::(pl.content()) { @@ -720,7 +718,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap(create.content()) @@ -758,7 +756,8 @@ pub fn get_send_level( content.events_default } }) - }).ok() + }) + .ok() }) .map(|int| i64::from(int)) .unwrap_or_else(|| if state_key.is_some() { 50 } else { 0 }) @@ -767,7 +766,7 @@ pub fn get_send_level( /// Check user can send invite. pub fn can_send_invite(event: &Arc, auth_events: &StateMap>) -> Result { let user_level = get_user_power_level(&event.sender(), auth_events); - let key = (EventType::RoomPowerLevels, Some("".into())); + let key = (EventType::RoomPowerLevels, "".into()); let invite_level = auth_events .get(&key) .map_or_else( diff --git a/src/lib.rs b/src/lib.rs index 99080d75..f41a0de7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub use state_store::StateStore; const _YIELD_AFTER_ITERATIONS: usize = 100; /// A mapping of event type and state_key to some value `T`, usually an `EventId`. -pub type StateMap = BTreeMap<(EventType, Option), T>; +pub type StateMap = BTreeMap<(EventType, String), T>; /// A mapping of `EventId` to `T`, usually a `ServerPdu`. pub type EventMap = BTreeMap; @@ -48,6 +48,8 @@ impl StateResolution { current_state: &StateMap, event_map: &EventMap>, ) -> Result { + let state_key = incoming_event.state_key().ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; + log::info!("Applying a single event, state resolution starting"); let ev = incoming_event; @@ -59,7 +61,7 @@ impl StateResolution { let mut auth_events = StateMap::new(); for key in - event_auth::auth_types_for_event(&ev.kind(), &ev.sender(), ev.state_key(), ev.content()) + event_auth::auth_types_for_event(&ev.kind(), &ev.sender(), Some(state_key), ev.content()) { if let Some(ev_id) = current_state.get(&key) { if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { @@ -183,7 +185,7 @@ impl StateResolution { ); // This "epochs" power level event - let power_event = resolved_control.get(&(EventType::RoomPowerLevels, Some("".into()))); + let power_event = resolved_control.get(&(EventType::RoomPowerLevels, "".into())); log::debug!("PL {:?}", power_event); @@ -498,12 +500,13 @@ impl StateResolution { for (idx, event_id) in events_to_check.iter().enumerate() { let event = StateResolution::get_or_load_event(room_id, event_id, event_map)?; + let state_key = event.state_key().ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; let mut auth_events = BTreeMap::new(); for aid in &event.auth_events() { if let Ok(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? - auth_events.insert((ev.kind(), ev.state_key()), ev); + auth_events.insert((ev.kind(), state_key.clone()), ev); } else { log::warn!("auth event id for {} is missing {}", aid, event_id); } @@ -512,7 +515,7 @@ impl StateResolution { for key in event_auth::auth_types_for_event( &event.kind(), &event.sender(), - event.state_key(), + Some(state_key.clone()), event.content(), ) { if let Some(ev_id) = resolved_state.get(&key) { @@ -550,7 +553,7 @@ impl StateResolution { current_third_party, )? { // add event to resolved state map - resolved_state.insert((event.kind(), event.state_key()), event_id.clone()); + resolved_state.insert((event.kind(), state_key), event_id.clone()); } else { // synapse passes here on AuthError. We do not add this event to resolved_state. log::warn!( diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 4d9c6fe7..46e14758 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -37,7 +37,7 @@ fn test_ban_pass() { ); assert!(valid_membership_change( - requester.state_key().as_deref(), + &requester.state_key(), requester.sender(), requester.content(), prev, @@ -72,7 +72,7 @@ fn test_ban_fail() { ); assert!(!valid_membership_change( - requester.state_key().as_deref(), + &requester.state_key(), requester.sender(), requester.content(), prev, diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 78688322..a1a56904 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -61,7 +61,7 @@ fn test_event_sort() { shuffle(&mut events_to_sort); - let power_level = resolved_power.get(&(EventType::RoomPowerLevels, Some("".to_string()))); + let power_level = resolved_power.get(&(EventType::RoomPowerLevels, "".to_string())); let sorted_event_ids = state_res::StateResolution::mainline_sort( &room_id(), diff --git a/tests/utils.rs b/tests/utils.rs index 7423eb0e..3d365092 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -132,16 +132,14 @@ pub fn do_check( let mut state_after = state_before.clone(); - if fake_event.state_key().is_some() { - let ty = fake_event.kind(); - let key = fake_event.state_key(); - state_after.insert((ty, key), event_id.clone()); - } + let ty = fake_event.kind(); + let key = fake_event.state_key(); + state_after.insert((ty, key), event_id.clone()); let auth_types = state_res::auth_types_for_event( &fake_event.kind(), fake_event.sender(), - fake_event.state_key(), + Some(fake_event.state_key()), fake_event.content(), ); @@ -160,7 +158,7 @@ pub fn do_check( e.event_id().as_str(), e.sender().clone(), e.kind().clone(), - e.state_key().as_deref(), + Some(&e.state_key()), e.content(), &auth_events, prev_events, @@ -555,7 +553,7 @@ pub mod event { } fn state_key(&self) -> Option { - self.state_key() + Some(self.state_key()) } fn prev_events(&self) -> Vec { self.prev_event_ids() @@ -796,11 +794,11 @@ pub mod event { }, } } - pub fn state_key(&self) -> Option { + pub fn state_key(&self) -> String { match self { Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), - Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), + Pdu::RoomV1Pdu(ev) => ev.state_key.clone().unwrap(), + Pdu::RoomV3Pdu(ev) => ev.state_key.clone().unwrap(), }, } } From 625c37cb776b381a83ab7ee58b13e32506849648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Fri, 26 Mar 2021 10:53:20 +0100 Subject: [PATCH 102/130] Fix room version updates --- src/event_auth.rs | 51 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 4f2d95bf..ae42bb55 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -171,9 +171,8 @@ pub fn auth_check( // [synapse] checks for federation here // 4. if type is m.room.aliases - if incoming_event.kind() == EventType::RoomAliases { + if incoming_event.kind() == EventType::RoomAliases && room_version < &RoomVersionId::Version6 { log::info!("starting m.room.aliases check"); - // [synapse] adds `&& room_version` "special case aliases auth" // If sender's domain doesn't matches state_key, reject if incoming_event.state_key() != Some(incoming_event.sender().server_name().to_string()) { @@ -263,7 +262,14 @@ pub fn auth_check( log::info!("power levels event allowed"); } - if incoming_event.kind() == EventType::RoomRedaction + // Room version 3: Redaction events are always accepted (provided the event is allowed by `events` and + // `events_default` in the power levels). However, servers should not apply or send redaction's + // to clients until both the redaction event and original event have been seen, and are valid. + // Servers should only apply redaction's to events where the sender's domains match, + // or the sender of the redaction has the appropriate permissions per the power levels. + + if room_version >= &RoomVersionId::Version3 + && incoming_event.kind() == EventType::RoomRedaction && !check_redaction(room_version, incoming_event, &auth_events)? { return Ok(false); @@ -470,7 +476,7 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>) /// Confirm that the event sender has the required power levels. pub fn check_power_levels( - _: &RoomVersionId, + room_version: &RoomVersionId, power_event: &Arc, auth_events: &StateMap>, ) -> Option { @@ -491,6 +497,7 @@ pub fn check_power_levels( power_event.content(), ) .unwrap(); + let current_content = serde_json::from_value::( current_state.content(), ) @@ -521,19 +528,6 @@ pub fn check_power_levels( log::debug!("events to check {:?}", event_levels_to_check); - // [synapse] validate MSC2209 depending on room version check "notifications". - // 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 { - // log::warn!("m.room.power_level cannot add ops > than own"); - // return Some(false); // cannot add ops greater than own - // } - // } - let old_state = ¤t_content; let new_state = &user_content; @@ -582,6 +576,22 @@ pub fn check_power_levels( } } + // Notifications, currently there is only @room + if room_version >= &RoomVersionId::Version6 { + let old_level = old_state.notifications.room; + let new_level = new_state.notifications.room; + if old_level != new_level { + // If the current value is higher than the sender's current power level, reject + // If the new value is higher than the sender's current power level, reject + let old_level_too_big = i64::from(old_level) > user_level; + let new_level_too_big = i64::from(new_level) > user_level; + if old_level_too_big || new_level_too_big { + log::warn!("m.room.power_level failed to add ops > than own"); + return Some(false); // cannot add ops greater than own + } + } + } + let levels = [ "users_default", "events_default", @@ -633,13 +643,6 @@ pub fn check_redaction( return Ok(true); } - // FROM SPEC: - // Redaction events are always accepted (provided the event is allowed by `events` and - // `events_default` in the power levels). However, servers should not apply or send redaction's - // to clients until both the redaction event and original event have been seen, and are valid. - // Servers should only apply redaction's to events where the sender's domains match, - // or the sender of the redaction has the appropriate permissions per the power levels. - // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow if redaction_event.event_id().server_name() == redaction_event From a0177669e67a74938d92c24f67a91d5679d927c0 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Fri, 26 Mar 2021 16:05:12 -0400 Subject: [PATCH 103/130] Fix all failing tests because of state_key unwraps --- Cargo.toml | 1 - benches/state_res_bench.rs | 15 --------------- rustfmt.toml | 2 +- src/lib.rs | 17 ++++++++++++----- tests/utils.rs | 28 ++++++++++------------------ 5 files changed, 23 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be90d6bf..9c88672b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec [features] default = ["unstable-pre-spec"] -gen-eventid = [] unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index dc8696c6..71732fde 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -561,21 +561,6 @@ pub mod event { event_id: EventId, } - /// This feature is turned on in conduit but off when the tests run because - /// we rely on the EventId to check the state-res. - #[cfg(feature = "gen-eventid")] - fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom) - } - - /// Only turned on for testing where we need to keep the ID. - #[cfg(not(feature = "gen-eventid"))] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; Ok(match from_raw_json_value::(&json) { diff --git a/rustfmt.toml b/rustfmt.toml index 7d2cf549..d3db3454 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1 @@ -merge_imports = true +imports_granularity="Crate" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f41a0de7..a5c94d9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,9 @@ impl StateResolution { current_state: &StateMap, event_map: &EventMap>, ) -> Result { - let state_key = incoming_event.state_key().ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; + let state_key = incoming_event + .state_key() + .ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; log::info!("Applying a single event, state resolution starting"); let ev = incoming_event; @@ -60,9 +62,12 @@ impl StateResolution { }; let mut auth_events = StateMap::new(); - for key in - event_auth::auth_types_for_event(&ev.kind(), &ev.sender(), Some(state_key), ev.content()) - { + for key in event_auth::auth_types_for_event( + &ev.kind(), + &ev.sender(), + Some(state_key), + ev.content(), + ) { if let Some(ev_id) = current_state.get(&key) { if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { // TODO synapse checks `rejected_reason` is None here @@ -500,7 +505,9 @@ impl StateResolution { for (idx, event_id) in events_to_check.iter().enumerate() { let event = StateResolution::get_or_load_event(room_id, event_id, event_map)?; - let state_key = event.state_key().ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; + let state_key = event + .state_key() + .ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; let mut auth_events = BTreeMap::new(); for aid in &event.auth_events() { diff --git a/tests/utils.rs b/tests/utils.rs index 3d365092..7c2f32cf 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -222,7 +222,14 @@ pub fn do_check( .get(&event_id("$END:foo")) .unwrap() .iter() - .filter(|(k, v)| expected_state.contains_key(k) || start_state.get(k) != Some(*v)) + .filter(|(k, v)| { + expected_state.contains_key(k) + || start_state.get(k) != Some(*v) + // Filter out the dummy messages events. + // These act as points in time where there should be a known state to + // test against. + && k != &&(EventType::RoomMessage, "dummy".to_string()) + }) .map(|(k, v)| (k.clone(), v.clone())) .collect::>(); @@ -482,7 +489,7 @@ pub fn INITIAL_EVENTS() -> BTreeMap> { "START", charlie(), EventType::RoomMessage, - None, + Some("dummy"), json!({}), &[], &[], @@ -491,7 +498,7 @@ pub fn INITIAL_EVENTS() -> BTreeMap> { "END", charlie(), EventType::RoomMessage, - None, + Some("dummy"), json!({}), &[], &[], @@ -585,21 +592,6 @@ pub mod event { event_id: EventId, } - /// This feature is turned on in conduit but off when the tests run because - /// we rely on the EventId to check the state-res. - #[cfg(feature = "gen-eventid")] - fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom) - } - - /// Only turned on for testing where we need to keep the ID. - #[cfg(not(feature = "gen-eventid"))] fn event_id(json: &RawJsonValue) -> Result { use std::convert::TryFrom; Ok(match from_raw_json_value::(&json) { From b7d465fabebd2290e4972827b10d2726c29a4699 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 5 Apr 2021 20:21:59 +0200 Subject: [PATCH 104/130] Upgrade ruma --- Cargo.toml | 13 +++++-------- benches/state_res_bench.rs | 13 +++++-------- src/event_auth.rs | 4 ++-- src/lib.rs | 5 +---- src/room_version.rs | 2 +- src/state_store.rs | 2 +- tests/event_sorting.rs | 5 +---- tests/res_with_auth_ids.rs | 5 +---- tests/state_res.rs | 2 +- tests/utils.rs | 5 +++-- 10 files changed, 21 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c88672b..92dc5ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,16 +19,13 @@ maplit = "1.0.2" thiserror = "1.0.22" log = "0.4.11" +[features] +unstable-pre-spec = ["ruma/unstable-pre-spec"] + [dependencies.ruma] git = "https://github.com/ruma/ruma" -#branch = "server-pdu" -#path = "../ruma/ruma" -rev = "f196f5b6f164973d6b343af31ab4e0457f743675" -features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] - -[features] -default = ["unstable-pre-spec"] -unstable-pre-spec = ["ruma/unstable-pre-spec"] +rev = "d883debe072086efffd4255dcca0a1a0d6fcd9ce" +features = ["events", "signatures"] [dev-dependencies] criterion = "0.3.3" diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 71732fde..b17f984e 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -16,7 +16,7 @@ use ruma::{ }, EventType, }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + EventId, RoomId, RoomVersionId, UserId, }; use serde_json::{json, Value as JsonValue}; use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; @@ -498,7 +498,7 @@ pub mod event { }, serde::CanonicalJsonValue, signatures::reference_hash, - EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, + EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId, }; use serde::{de, ser, Deserialize, Serialize}; use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; @@ -546,9 +546,7 @@ pub mod event { fn hashes(&self) -> &EventHash { self.hashes() } - fn signatures( - &self, - ) -> BTreeMap, BTreeMap> { + fn signatures(&self) -> BTreeMap, BTreeMap> { self.signatures() } fn unsigned(&self) -> &BTreeMap { @@ -675,9 +673,8 @@ pub mod event { | EventType::RoomJoinRules | EventType::RoomCreate => event.state_key == Some("".into()), EventType::RoomMember => { + // TODO fix clone if let Ok(content) = - // TODO fix clone - serde_json::from_value::(event.content.clone()) { if [MembershipState::Leave, MembershipState::Ban] @@ -817,7 +814,7 @@ pub mod event { pub fn signatures( &self, - ) -> BTreeMap, BTreeMap> { + ) -> BTreeMap, BTreeMap> { match self { Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, diff --git a/src/event_auth.rs b/src/event_auth.rs index ae42bb55..9d6a9611 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -11,7 +11,7 @@ use ruma::{ }, EventType, }, - identifiers::{RoomVersionId, UserId}, + RoomVersionId, UserId, }; use crate::{Error, Event, Result, StateMap}; @@ -762,7 +762,7 @@ pub fn get_send_level( }) .ok() }) - .map(|int| i64::from(int)) + .map(i64::from) .unwrap_or_else(|| if state_key.is_some() { 50 } else { 0 }) } diff --git a/src/lib.rs b/src/lib.rs index a5c94d9b..ac621b1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,10 +6,7 @@ use std::{ }; use maplit::btreeset; -use ruma::{ - events::EventType, - identifiers::{EventId, RoomId, RoomVersionId}, -}; +use ruma::{events::EventType, EventId, RoomId, RoomVersionId}; mod error; pub mod event_auth; diff --git a/src/room_version.rs b/src/room_version.rs index 1cf86b92..a8db26ea 100644 --- a/src/room_version.rs +++ b/src/room_version.rs @@ -1,4 +1,4 @@ -use ruma::identifiers::RoomVersionId; +use ruma::RoomVersionId; pub enum RoomDisposition { /// A room version that has a stable specification. diff --git a/src/state_store.rs b/src/state_store.rs index ada65caa..b8a1ea88 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeSet, sync::Arc}; -use ruma::identifiers::{EventId, RoomId}; +use ruma::{EventId, RoomId}; use crate::{Event, Result}; diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index a1a56904..81e7aefd 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -1,9 +1,6 @@ use std::collections::BTreeMap; -use ruma::{ - events::EventType, - identifiers::{EventId, RoomVersionId}, -}; +use ruma::{events::EventType, EventId, RoomVersionId}; use state_res::{is_power_event, StateMap}; mod utils; diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index de99ac4e..24af024f 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -2,10 +2,7 @@ use std::{collections::BTreeMap, sync::Arc}; -use ruma::{ - events::EventType, - identifiers::{EventId, RoomVersionId}, -}; +use ruma::{events::EventType, EventId, RoomVersionId}; use serde_json::json; use state_res::{EventMap, StateMap, StateResolution, StateStore}; diff --git a/tests/state_res.rs b/tests/state_res.rs index 9d72994d..36307926 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -3,7 +3,7 @@ use std::{sync::Arc, time::UNIX_EPOCH}; use maplit::btreemap; use ruma::{ events::{room::join_rules::JoinRule, EventType}, - identifiers::{EventId, RoomVersionId}, + EventId, RoomVersionId, }; use serde_json::json; use state_res::{StateMap, StateResolution, StateStore}; diff --git a/tests/utils.rs b/tests/utils.rs index 7c2f32cf..91d51838 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -15,7 +15,7 @@ use ruma::{ }, EventType, }, - identifiers::{EventId, RoomId, RoomVersionId, UserId}, + EventId, RoomId, RoomVersionId, UserId, }; use serde_json::{json, Value as JsonValue}; use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; @@ -706,8 +706,8 @@ pub mod event { | EventType::RoomJoinRules | EventType::RoomCreate => event.state_key == Some("".into()), EventType::RoomMember => { + // TODO fix clone if let Ok(content) = - // TODO fix clone serde_json::from_value::(event.content.clone()) { if [MembershipState::Leave, MembershipState::Ban] @@ -921,6 +921,7 @@ pub mod event { } #[test] + #[cfg_attr(not(feature = "unstable-pre-spec"), ignore)] fn serialize_pdu() { let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; From af450d0fe2b0e1c890284d0bc3b9d6d4008ac475 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 6 Apr 2021 17:28:36 +0200 Subject: [PATCH 105/130] Bump ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 92dc5ea8..730bbfce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "d883debe072086efffd4255dcca0a1a0d6fcd9ce" +rev = "a310ccc318a4eb51062923d570d5a86c1468e8a1" features = ["events", "signatures"] [dev-dependencies] From e8ebe07609ea5613bd61416d4865707746abb364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Thu, 8 Apr 2021 16:40:54 +0200 Subject: [PATCH 106/130] Avoid deserializing to MemberEventContent deserializing doesn't work when the event contains bad content in unimportant fields (like a non-mxc url as avatar) --- src/event_auth.rs | 78 +++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 9d6a9611..6ea01a0e 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -36,9 +36,11 @@ pub fn auth_types_for_event( if kind == &EventType::RoomMember { if let Some(state_key) = state_key { - if let Ok(content) = serde_json::from_value::(content) + if let Some(Ok(membership)) = content + .get("membership") + .map(|m| serde_json::from_value::(m.clone())) { - if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) { + if [MembershipState::Join, MembershipState::Invite].contains(&membership) { let key = (EventType::RoomJoinRules, "".to_string()); if !auth_types.contains(&key) { auth_types.push(key) @@ -50,8 +52,10 @@ pub fn auth_types_for_event( auth_types.push(key) } - if content.membership == MembershipState::Invite { - if let Some(t_id) = content.third_party_invite { + if membership == MembershipState::Invite { + if let Some(Ok(t_id)) = content.get("third_party_invite").map(|t| { + serde_json::from_value::(t.clone()) + }) { let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); if !auth_types.contains(&key) { auth_types.push(key) @@ -194,10 +198,13 @@ pub fn auth_check( Some(s) => s, }; - if serde_json::from_value::(incoming_event.content()) - .is_err() - { - log::warn!("no membership filed found for m.room.member event content"); + let membership = incoming_event + .content() + .get("membership") + .map(|m| serde_json::from_value::(m.clone())); + + if !matches!(membership, Some(Ok(_))) { + log::warn!("no valid membership field found for m.room.member event content"); return Ok(false); } @@ -296,9 +303,16 @@ pub fn valid_membership_change( current_third_party_invite: Option>, auth_events: &StateMap>, ) -> Result { - let content = serde_json::from_value::(content)?; + let target_membership = serde_json::from_value::( + content + .get("membership") + .expect("we should test before that this field exists") + .clone(), + )?; - let target_membership = content.membership; + let third_party_invite = content + .get("third_party_invite") + .map(|t| serde_json::from_value::(t.clone())); let target_user_id = UserId::try_from(state_key).map_err(|e| Error::ConversionError(format!("{}", e)))?; @@ -307,20 +321,24 @@ pub fn valid_membership_change( let sender = auth_events.get(&key); let sender_membership = sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok( - serde_json::from_value::(pdu.content())? - .membership, - ) + Ok(serde_json::from_value::( + pdu.content() + .get("membership") + .expect("we assume existing events are valid") + .clone(), + )?) })?; let key = (EventType::RoomMember, target_user_id.to_string()); let current = auth_events.get(&key); let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok( - serde_json::from_value::(pdu.content())? - .membership, - ) + Ok(serde_json::from_value::( + pdu.content() + .get("membership") + .expect("we assume existing events are valid") + .clone(), + )?) })?; let key = (EventType::RoomPowerLevels, "".into()); @@ -383,7 +401,7 @@ pub fn valid_membership_change( } } else if target_membership == MembershipState::Invite { // If content has third_party_invite key - if let Some(tp_id) = content.third_party_invite { + if let Some(Ok(tp_id)) = third_party_invite { if current_membership == MembershipState::Ban { false } else { @@ -435,12 +453,16 @@ pub fn check_event_sender_in_room( auth_events: &StateMap>, ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; - Some( - serde_json::from_value::(mem.content()) - .ok()? - .membership - == MembershipState::Join, + + let membership = serde_json::from_value::( + mem.content() + .get("membership") + .expect("we should test before that this field exists") + .clone(), ) + .ok()?; + + Some(membership == MembershipState::Join) } /// Is the user allowed to send a specific event based on the rooms power levels. Does the event @@ -662,10 +684,12 @@ pub fn check_redaction( /// This function returns false instead of failing when deserialization fails. pub fn check_membership(member_event: Option>, state: MembershipState) -> bool { if let Some(event) = member_event { - if let Ok(content) = - serde_json::from_value::(event.content()) + if let Some(Ok(membership)) = event + .content() + .get("membership") + .map(|m| serde_json::from_value::(m.clone())) { - content.membership == state + membership == state } else { false } From 2ef7730ebbf5a8fae74c829ee5058a1eb9c6a61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 13 Apr 2021 11:35:10 +0200 Subject: [PATCH 107/130] More warnings --- src/event_auth.rs | 65 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 6ea01a0e..0f6b6942 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -1,5 +1,6 @@ use std::{convert::TryFrom, sync::Arc}; +use log::warn; use maplit::btreeset; use ruma::{ events::{ @@ -87,7 +88,11 @@ pub fn auth_check( auth_events: &StateMap>, current_third_party_invite: Option>, ) -> Result { - log::info!("auth_check beginning for {}", incoming_event.kind()); + log::info!( + "auth_check beginning for {} ({})", + incoming_event.event_id(), + incoming_event.kind() + ); // [synapse] check that all the events are in the same room as `incoming_event` @@ -390,59 +395,93 @@ pub fn valid_membership_change( Ok(if target_membership == MembershipState::Join { if user_sender != &target_user_id { + warn!("Can't make other user join"); false } else if let MembershipState::Ban = current_membership { + warn!("Banned user can't join"); false } else { - join_rules == JoinRule::Invite + let allow = join_rules == JoinRule::Invite && (current_membership == MembershipState::Join || current_membership == MembershipState::Invite) - || join_rules == JoinRule::Public + || join_rules == JoinRule::Public; + + if !allow { + warn!("Can't join if join rules is not public and user is not invited/joined") + } + allow } } else if target_membership == MembershipState::Invite { // If content has third_party_invite key if let Some(Ok(tp_id)) = third_party_invite { if current_membership == MembershipState::Ban { + warn!("Can't invite banned user"); false } else { - verify_third_party_invite( + let allow = verify_third_party_invite( Some(state_key), user_sender, &tp_id, current_third_party_invite, - ) + ); + if !allow { + warn!("Third party invite invalid"); + } + allow } } else if sender_membership != MembershipState::Join || current_membership == MembershipState::Join || current_membership == MembershipState::Ban { + warn!( + "Can't invite user if sender not joined or the user is currently joined or banned" + ); false } else { - sender_power + let allow = sender_power .filter(|&p| p >= &power_levels.invite) - .is_some() + .is_some(); + if !allow { + warn!("User does not have enough power to invite"); + } + allow } } else if target_membership == MembershipState::Leave { if user_sender == &target_user_id { - current_membership == MembershipState::Join - || current_membership == MembershipState::Invite + let allow = current_membership == MembershipState::Join + || current_membership == MembershipState::Invite; + if !allow { + warn!("Can't leave if not invited or joined"); + } + allow } else if sender_membership != MembershipState::Join || current_membership == MembershipState::Ban && sender_power.filter(|&p| p < &power_levels.ban).is_some() { + warn!("Can't kick if sender not joined or user is already banned"); false } else { - sender_power.filter(|&p| p >= &power_levels.kick).is_some() - && target_power < sender_power + let allow = sender_power.filter(|&p| p >= &power_levels.kick).is_some() + && target_power < sender_power; + if !allow { + warn!("User does not have enough power to kick"); + } + allow } } else if target_membership == MembershipState::Ban { if sender_membership != MembershipState::Join { + warn!("Can't ban user if sender is not joined"); false } else { - sender_power.filter(|&p| p >= &power_levels.ban).is_some() - && target_power < sender_power + let allow = sender_power.filter(|&p| p >= &power_levels.ban).is_some() + && target_power < sender_power; + if !allow { + warn!("User does not have enough power to ban"); + } + allow } } else { + warn!("Unknown membership transition"); false }) } From 4015ced5eab82e69568a2441106b8683d76a08e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Sun, 11 Apr 2021 20:59:04 +0200 Subject: [PATCH 108/130] Bump ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 730bbfce..66f9ba30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "a310ccc318a4eb51062923d570d5a86c1468e8a1" +rev = "c1693569f15920e408aa6a26b7f3cc7fc6693a63" features = ["events", "signatures"] [dev-dependencies] From 30b4e1d597867d1abbc410946ce758cf167d7624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 13 Apr 2021 21:31:43 +0200 Subject: [PATCH 109/130] fix: use correct state key --- src/event_auth.rs | 1 + src/lib.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 0f6b6942..a2e62e34 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -336,6 +336,7 @@ pub fn valid_membership_change( let key = (EventType::RoomMember, target_user_id.to_string()); let current = auth_events.get(&key); + let current_membership = current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { Ok(serde_json::from_value::( diff --git a/src/lib.rs b/src/lib.rs index ac621b1b..ab4f2681 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -510,7 +510,15 @@ impl StateResolution { for aid in &event.auth_events() { if let Ok(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? - auth_events.insert((ev.kind(), state_key.clone()), ev); + auth_events.insert( + ( + ev.kind(), + ev.state_key().ok_or_else(|| { + Error::InvalidPdu("State event had no state key".to_owned()) + })?, + ), + ev, + ); } else { log::warn!("auth event id for {} is missing {}", aid, event_id); } From 4516d73e8c7495330619bfb5b42c3bbf704293d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Wed, 14 Apr 2021 09:10:28 +0200 Subject: [PATCH 110/130] fix: use users_default power level --- src/event_auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index a2e62e34..25aab27a 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -778,7 +778,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap Date: Thu, 22 Apr 2021 18:31:05 +0200 Subject: [PATCH 111/130] Bump ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 66f9ba30..5c0fa7ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "c1693569f15920e408aa6a26b7f3cc7fc6693a63" +rev = "4f16b9357c15d649075393a723f23cf560251754" features = ["events", "signatures"] [dev-dependencies] From 1dd252d1c97a38def74bc097c197a33179ed8fbb Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 23 Apr 2021 18:04:51 +0200 Subject: [PATCH 112/130] Bump ruma dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5c0fa7ae..27ccb11a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "4f16b9357c15d649075393a723f23cf560251754" +rev = "12ec0fb1680ebc4fec4fbefbbd0890ae4eaf3a88" features = ["events", "signatures"] [dev-dependencies] From 8103bbcf6a701bd981090aca1b68f886577d29b6 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 24 Apr 2021 15:28:57 -0400 Subject: [PATCH 113/130] Clean up unused bits add TODOs --- src/lib.rs | 74 ++++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab4f2681..f86a3f6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,16 @@ use std::{ }; use maplit::btreeset; -use ruma::{events::EventType, EventId, RoomId, RoomVersionId}; +use ruma::{ + events::{ + room::{ + member::{MemberEventContent, MembershipState}, + power_levels::PowerLevelsEventContent, + }, + EventType, + }, + EventId, RoomId, RoomVersionId, +}; mod error; pub mod event_auth; @@ -19,11 +28,6 @@ pub use event_auth::{auth_check, auth_types_for_event}; pub use state_event::Event; pub use state_store::StateStore; -// We want to yield to the reactor occasionally during state res when dealing -// with large data sets, so that we don't exhaust the reactor. This is done by -// yielding to reactor during loops every N iterations. -const _YIELD_AFTER_ITERATIONS: usize = 100; - /// A mapping of event type and state_key to some value `T`, usually an `EventId`. pub type StateMap = BTreeMap<(EventType, String), T>; @@ -306,31 +310,27 @@ impl StateResolution { log::debug!("reverse topological sort of power events"); let mut graph = BTreeMap::new(); - for (idx, event_id) in events_to_sort.iter().enumerate() { + for event_id in events_to_sort.iter() { StateResolution::add_event_and_auth_chain_to_graph( room_id, &mut graph, event_id, event_map, 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) - } + // TODO: if these functions are ever made async here + // is a good place to yield every once in a while so other + // "threads" can make progress } // 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() { + for event_id in graph.keys() { let pl = StateResolution::get_power_level_for_sender(room_id, &event_id, event_map); log::info!("{} power level {}", event_id.to_string(), pl); 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) - } + // TODO: if these functions are ever made async here + // is a good place to yield every once in a while so other + // "threads" can make progress } StateResolution::lexicographical_topological_sort(&graph, |event_id| { @@ -451,12 +451,7 @@ impl StateResolution { } if let Some(content) = pl - .map(|pl| { - serde_json::from_value::( - pl.content(), - ) - .ok() - }) + .map(|pl| serde_json::from_value::(pl.content()).ok()) .flatten() { if let Ok(ev) = event { @@ -500,7 +495,7 @@ impl StateResolution { let mut resolved_state = unconflicted_state.clone(); - for (idx, event_id) in events_to_check.iter().enumerate() { + for event_id in events_to_check.iter() { let event = StateResolution::get_or_load_event(room_id, event_id, event_map)?; let state_key = event .state_key() @@ -574,11 +569,9 @@ impl StateResolution { ); } - // 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) - } + // TODO: if these functions are ever made async here + // is a good place to yield every once in a while so other + // "threads" can make progress } Ok(resolved_state) } @@ -605,7 +598,6 @@ impl StateResolution { let mut mainline = vec![]; let mut pl = resolved_power_level.cloned(); - let mut idx = 0; while let Some(p) = pl { mainline.push(p.clone()); @@ -619,12 +611,9 @@ impl StateResolution { break; } } - // 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 != 0 && idx % _YIELD_AFTER_ITERATIONS == 0 { - // yield clock.sleep(0) - } - idx += 1; + // TODO: if these functions are ever made async here + // is a good place to yield every once in a while so other + // "threads" can make progress } let mainline_map = mainline @@ -635,7 +624,7 @@ impl StateResolution { .collect::>(); let mut order_map = BTreeMap::new(); - for (idx, ev_id) in to_sort.iter().enumerate() { + for ev_id in to_sort.iter() { if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { if let Ok(depth) = StateResolution::get_mainline_depth( room_id, @@ -654,11 +643,9 @@ impl StateResolution { } } - // 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) - } + // TODO: if these functions are ever made async here + // is a good place to yield every once in a while so other + // "threads" can make progress } // sort the event_ids by their depth, timestamp and EventId @@ -755,7 +742,6 @@ pub fn is_type_and_key(ev: &Arc, ev_type: EventType, state_key: &st } pub fn is_power_event(event: &Arc) -> bool { - use ruma::events::room::member::{MemberEventContent, MembershipState}; match event.kind() { EventType::RoomPowerLevels | EventType::RoomJoinRules | EventType::RoomCreate => { event.state_key() == Some("".into()) From a9e248da34bddb549ccba45a01a8ce4aed85c1bd Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Sat, 24 Apr 2021 16:19:17 -0400 Subject: [PATCH 114/130] Replace threads -> tasks in TODO comments --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f86a3f6a..6f88c315 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -317,7 +317,7 @@ impl StateResolution { // TODO: if these functions are ever made async here // is a good place to yield every once in a while so other - // "threads" can make progress + // tasks can make progress } // this is used in the `key_fn` passed to the lexico_topo_sort fn @@ -330,7 +330,7 @@ impl StateResolution { // TODO: if these functions are ever made async here // is a good place to yield every once in a while so other - // "threads" can make progress + // tasks can make progress } StateResolution::lexicographical_topological_sort(&graph, |event_id| { @@ -571,7 +571,7 @@ impl StateResolution { // TODO: if these functions are ever made async here // is a good place to yield every once in a while so other - // "threads" can make progress + // tasks can make progress } Ok(resolved_state) } @@ -613,7 +613,7 @@ impl StateResolution { } // TODO: if these functions are ever made async here // is a good place to yield every once in a while so other - // "threads" can make progress + // tasks can make progress } let mainline_map = mainline @@ -645,7 +645,7 @@ impl StateResolution { // TODO: if these functions are ever made async here // is a good place to yield every once in a while so other - // "threads" can make progress + // tasks can make progress } // sort the event_ids by their depth, timestamp and EventId From c2988d4b8d34acb39cff4b3104f016520594cc46 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 26 Apr 2021 09:30:48 -0400 Subject: [PATCH 115/130] Remove StateResolution::apply_event, fix test serde make pdu struct --- benches/outcomes.txt | 22 +- benches/state_res_bench.rs | 377 +++++++++------------------- src/lib.rs | 42 ---- tests/utils.rs | 496 +++++++++++-------------------------- 4 files changed, 282 insertions(+), 655 deletions(-) diff --git a/benches/outcomes.txt b/benches/outcomes.txt index 2de257ec..7696c06b 100644 --- a/benches/outcomes.txt +++ b/benches/outcomes.txt @@ -36,4 +36,24 @@ resolve state of 10 events 3 conflicting change: [-0.8433% -0.3270% +0.2656%] (p = 0.25 > 0.05) No change in performance detected. Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) high mild \ No newline at end of file + 1 (1.00%) high mild + +4/26/2020 BRANCH: fix-test-serde REV: +lexicographical topological sort + time: [1.6793 us 1.6823 us 1.6857 us] +Found 9 outliers among 100 measurements (9.00%) + 1 (1.00%) low mild + 4 (4.00%) high mild + 4 (4.00%) high severe + +resolve state of 5 events one fork + time: [9.9993 us 10.062 us 10.159 us] +Found 9 outliers among 100 measurements (9.00%) + 7 (7.00%) high mild + 2 (2.00%) high severe + +resolve state of 10 events 3 conflicting + time: [26.004 us 26.092 us 26.195 us] +Found 16 outliers among 100 measurements (16.00%) + 11 (11.00%) high mild + 5 (5.00%) high severe \ No newline at end of file diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index b17f984e..99143284 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -4,12 +4,19 @@ // `cargo bench unknown option --save-baseline`. // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. -use std::{collections::BTreeMap, convert::TryFrom, sync::Arc, time::UNIX_EPOCH}; +use std::{ + collections::BTreeMap, + convert::TryFrom, + sync::Arc, + time::{Duration, UNIX_EPOCH}, +}; use criterion::{criterion_group, criterion_main, Criterion}; +use event::StateEvent; use maplit::btreemap; use ruma::{ events::{ + pdu::{EventHash, Pdu, RoomV3Pdu}, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -21,7 +28,7 @@ use ruma::{ use serde_json::{json, Value as JsonValue}; use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; -static mut SERVER_TIMESTAMP: i32 = 0; +static mut SERVER_TIMESTAMP: u64 = 0; fn lexico_topo_sort(c: &mut Criterion) { c.bench_function("lexicographical topological sort", |b| { @@ -289,7 +296,7 @@ fn member_content_join() -> JsonValue { .unwrap() } -fn to_pdu_event( +pub fn to_pdu_event( id: &str, sender: UserId, ev_type: EventType, @@ -297,7 +304,7 @@ fn to_pdu_event( content: JsonValue, auth_events: &[S], prev_events: &[S], -) -> Arc +) -> Arc where S: AsRef, { @@ -323,39 +330,27 @@ where .map(event_id) .collect::>(); - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) + let state_key = state_key.map(ToString::to_string); + Arc::new(StateEvent { + event_id: EventId::try_from(id).unwrap(), + rest: Pdu::RoomV3Pdu(RoomV3Pdu { + room_id: room_id(), + sender, + origin_server_ts: UNIX_EPOCH + Duration::from_secs(ts), + state_key, + kind: ev_type, + content, + redacts: None, + unsigned: btreemap! {}, + #[cfg(not(feature = "unstable-pre-spec"))] + origin: "foo".into(), + auth_events, + prev_events, + depth: ruma::uint!(0), + hashes: EventHash { sha256: "".into() }, + signatures: btreemap! {}, + }), + }) } // all graphs start with these input events @@ -491,17 +486,14 @@ pub mod event { use ruma::{ events::{ - from_raw_json_value, pdu::{EventHash, Pdu}, room::member::{MemberEventContent, MembershipState}, - EventDeHelper, EventType, + EventType, }, - serde::CanonicalJsonValue, - signatures::reference_hash, EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId, }; - use serde::{de, ser, Deserialize, Serialize}; - use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; + use serde::{Deserialize, Serialize}; + use serde_json::Value as JsonValue; use state_res::Event; @@ -555,92 +547,10 @@ pub mod event { } #[derive(Clone, Debug, Deserialize, Serialize)] - struct EventIdHelper { - event_id: EventId, - } - - fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - Ok(match from_raw_json_value::(&json) { - Ok(id) => id.event_id, - Err(_) => { - // panic!("NOT DURING TESTS"); - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom)? - } - }) - } - - // TODO: This no longer needs to be an enum now that PduStub is gone - #[derive(Clone, Debug)] - pub enum StateEvent { - Full(EventId, Pdu), - } - - impl Serialize for StateEvent { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - use ser::Error; - use std::convert::TryInto; - - match self { - Self::Full(id, ev) => { - // TODO: do we want to add the eventId when we - // serialize - let val: CanonicalJsonValue = serde_json::to_value(ev) - .map_err(S::Error::custom)? - .try_into() - .map_err(S::Error::custom)?; - - match val { - CanonicalJsonValue::Object(mut obj) => { - obj.insert( - "event_id".into(), - ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, - ); - obj.serialize(serializer) - } - _ => panic!("Pdu not an object"), - } - } - } - } - } - - impl<'de> de::Deserialize<'de> for StateEvent { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let json = Box::::deserialize(deserializer)?; - let EventDeHelper { - room_id, unsigned, .. - } = from_raw_json_value(&json)?; - - // TODO: do we even want to try for the existing ID - - // Determine whether the event is a full or stub - // based on the fields present. - Ok(if room_id.is_some() { - match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Full( - event_id(&json)?, - Pdu::RoomV3Pdu(from_raw_json_value(&json)?), - ), - } - } else { - panic!("Found stub event") - }) - } + pub struct StateEvent { + pub event_id: EventId, + #[serde(flatten)] + pub rest: Pdu, } impl StateEvent { @@ -648,209 +558,168 @@ pub mod event { id: EventId, json: serde_json::Value, ) -> Result { - Ok(Self::Full( - id, - Pdu::RoomV3Pdu(serde_json::from_value(json)?), - )) + Ok(Self { + event_id: id, + rest: Pdu::RoomV3Pdu(serde_json::from_value(json)?), + }) } pub fn from_id_canon_obj( id: EventId, json: ruma::serde::CanonicalJsonObject, ) -> Result { - Ok(Self::Full( - id, + Ok(Self { + event_id: id, // TODO: this is unfortunate (from_value(to_value(json)))... - Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), - )) + rest: Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), + }) } pub fn is_power_event(&self) -> bool { - match self { - Self::Full(_, any_event) => match any_event { - Pdu::RoomV1Pdu(event) => match event.kind { - EventType::RoomPowerLevels - | EventType::RoomJoinRules - | EventType::RoomCreate => event.state_key == Some("".into()), - EventType::RoomMember => { - // TODO fix clone - if let Ok(content) = - serde_json::from_value::(event.content.clone()) + match &self.rest { + Pdu::RoomV1Pdu(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + // TODO fix clone + if let Ok(content) = + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() + return event.sender.as_str() // TODO is None here a failure != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); - } } - - false } - _ => false, - }, - Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), + + false + } + _ => false, }, + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), } } pub fn deserialize_content( &self, ) -> Result { - 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()), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()), + Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()), } } pub fn origin_server_ts(&self) -> &SystemTime { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, - Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, + Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, } } pub fn event_id(&self) -> &EventId { - match self { - // TODO; make this a &EventId - Self::Full(id, _) => id, - } + &self.event_id } pub fn sender(&self) -> &UserId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.sender, - Pdu::RoomV3Pdu(ev) => &ev.sender, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.sender, + Pdu::RoomV3Pdu(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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(), + Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(), } } pub fn room_id(&self) -> &RoomId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.room_id, - Pdu::RoomV3Pdu(ev) => &ev.room_id, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.room_id, + Pdu::RoomV3Pdu(ev) => &ev.room_id, } } pub fn kind(&self) -> EventType { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.kind.clone(), - Pdu::RoomV3Pdu(ev) => ev.kind.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.kind.clone(), + Pdu::RoomV3Pdu(ev) => ev.kind.clone(), } } pub fn state_key(&self) -> Option { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), - Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), + Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), } } #[cfg(not(feature = "unstable-pre-spec"))] pub fn origin(&self) -> String { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.origin.clone(), - Pdu::RoomV3Pdu(ev) => ev.origin.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.origin.clone(), + Pdu::RoomV3Pdu(ev) => ev.origin.clone(), } } pub fn prev_event_ids(&self) -> Vec { - 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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), } } pub fn auth_events(&self) -> Vec { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => { - ev.auth_events.iter().map(|(id, _)| id).cloned().collect() - } - Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), } } 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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.content.clone(), + Pdu::RoomV3Pdu(ev) => ev.content.clone(), } } pub fn unsigned(&self) -> &BTreeMap { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.unsigned, - Pdu::RoomV3Pdu(ev) => &ev.unsigned, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.unsigned, + Pdu::RoomV3Pdu(ev) => &ev.unsigned, } } pub fn signatures( &self, - ) -> BTreeMap, BTreeMap> { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, - Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), - }, + ) -> BTreeMap, BTreeMap> { + match &self.rest { + Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, + Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), } } pub fn hashes(&self) -> &EventHash { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.hashes, - Pdu::RoomV3Pdu(ev) => &ev.hashes, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.hashes, + Pdu::RoomV3Pdu(ev) => &ev.hashes, } } pub fn depth(&self) -> &UInt { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.depth, - Pdu::RoomV3Pdu(ev) => &ev.depth, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.depth, + Pdu::RoomV3Pdu(ev) => &ev.depth, } } pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { - 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) - } - }, + match &self.rest { + 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) + } } } @@ -860,11 +729,9 @@ pub mod event { /// version 3 and above. pub fn room_version(&self) -> RoomVersionId { // TODO: We have to know the actual room version this is not sufficient - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, - Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, - }, + match self.rest { + Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, } } } diff --git a/src/lib.rs b/src/lib.rs index 6f88c315..569b4575 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,48 +38,6 @@ pub type EventMap = BTreeMap; pub struct StateResolution; impl StateResolution { - /// Check if the `incoming_event` can be included in the given `current_state`. - /// - /// This will authenticate the event against the current state of the room. It - /// is important that the `current_state` argument is accurate and complete. - pub fn apply_event( - room_id: &RoomId, - room_version: &RoomVersionId, - incoming_event: Arc, - current_state: &StateMap, - event_map: &EventMap>, - ) -> Result { - let state_key = incoming_event - .state_key() - .ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; - - log::info!("Applying a single event, state resolution starting"); - let ev = incoming_event; - - let prev_event = if let Some(id) = ev.prev_events().first() { - event_map.get(id).map(Arc::clone) - } else { - None - }; - - let mut auth_events = StateMap::new(); - for key in event_auth::auth_types_for_event( - &ev.kind(), - &ev.sender(), - Some(state_key), - ev.content(), - ) { - if let Some(ev_id) = current_state.get(&key) { - if let Ok(event) = StateResolution::get_or_load_event(room_id, ev_id, event_map) { - // TODO synapse checks `rejected_reason` is None here - auth_events.insert(key.clone(), event); - } - } - } - - event_auth::auth_check(room_version, &ev, prev_event, &auth_events, None) - } - /// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph /// and an auth chain to allow for state conflict resolution. /// diff --git a/tests/utils.rs b/tests/utils.rs index 91d51838..c3d81959 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -4,11 +4,13 @@ use std::{ collections::BTreeMap, convert::TryFrom, sync::{Arc, Once}, - time::UNIX_EPOCH, + time::{Duration, UNIX_EPOCH}, }; +use maplit::btreemap; use ruma::{ events::{ + pdu::{EventHash, Pdu, RoomV3Pdu}, room::{ join_rules::JoinRule, member::{MemberEventContent, MembershipState}, @@ -25,7 +27,7 @@ pub use event::StateEvent; pub static LOGGER: Once = Once::new(); -static mut SERVER_TIMESTAMP: i32 = 0; +static mut SERVER_TIMESTAMP: u64 = 0; pub fn do_check( events: &[Arc], @@ -39,8 +41,10 @@ pub fn do_check( .init() }); + let init_events = INITIAL_EVENTS(); + let mut store = TestStore( - INITIAL_EVENTS() + init_events .values() .chain(events) .map(|ev| (ev.event_id().clone(), ev.clone())) @@ -54,7 +58,7 @@ pub fn do_check( // create the DB of events that led up to this point // TODO maybe clean up some of these clones it is just tests but... - for ev in INITIAL_EVENTS().values().chain(events) { + for ev in init_events.values().chain(events) { graph.insert(ev.event_id().clone(), vec![]); fake_event_map.insert(ev.event_id().clone(), ev.clone()); } @@ -164,34 +168,6 @@ pub fn do_check( prev_events, ); - // This can be used to sort of test this function - // match StateResolution::apply_event( - // &room_id(), - // &RoomVersionId::Version6, - // Arc::clone(&event), - // &state_after, - // Some(event_map.clone()), - // &store, - // ) { - // Ok(res) => { - // println!( - // "res contains: {} passed: {} for {}\n{:?}", - // state_after - // .get(&(event.kind, event.state_key())) - // .map(|id| id == &ev_id) - // .unwrap_or_default(), - // res, - // event.event_id.clone().as_str(), - // event - // .prev_event_ids() - // .iter() - // .map(|id| id.to_string()) - // .collect::>() - // ); - // } - // Err(e) => panic!("resolution for {} failed: {}", node, e), - // } - // we have to update our store, an actual user of this lib would // be giving us state from a DB. store.0.insert(ev_id.clone(), event.clone()); @@ -316,39 +292,27 @@ pub fn to_init_pdu_event( format!("${}:foo", id) }; - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": [], - "prev_events": [], - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": [], - "prev_events": [], - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) + let state_key = state_key.map(ToString::to_string); + Arc::new(StateEvent { + event_id: EventId::try_from(id).unwrap(), + rest: Pdu::RoomV3Pdu(RoomV3Pdu { + room_id: room_id(), + sender, + origin_server_ts: UNIX_EPOCH + Duration::from_secs(ts), + state_key, + kind: ev_type, + content, + redacts: None, + unsigned: btreemap! {}, + #[cfg(not(feature = "unstable-pre-spec"))] + origin: "foo".into(), + auth_events: vec![], + prev_events: vec![], + depth: ruma::uint!(0), + hashes: EventHash { sha256: "".into() }, + signatures: btreemap! {}, + }), + }) } pub fn to_pdu_event( @@ -385,39 +349,27 @@ where .map(event_id) .collect::>(); - let json = if let Some(state_key) = state_key { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "state_key": state_key, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - } else { - json!({ - "auth_events": auth_events, - "prev_events": prev_events, - "event_id": id, - "sender": sender, - "type": ev_type, - "content": content, - "origin_server_ts": ts, - "room_id": room_id(), - "origin": "foo", - "depth": 0, - "hashes": { "sha256": "hello" }, - "signatures": {}, - }) - }; - Arc::new(serde_json::from_value(json).unwrap()) + let state_key = state_key.map(ToString::to_string); + Arc::new(StateEvent { + event_id: EventId::try_from(id).unwrap(), + rest: Pdu::RoomV3Pdu(RoomV3Pdu { + room_id: room_id(), + sender, + origin_server_ts: UNIX_EPOCH + Duration::from_secs(ts), + state_key, + kind: ev_type, + content, + redacts: None, + unsigned: btreemap! {}, + #[cfg(not(feature = "unstable-pre-spec"))] + origin: "foo".into(), + auth_events, + prev_events, + depth: ruma::uint!(0), + hashes: EventHash { sha256: "".into() }, + signatures: btreemap! {}, + }), + }) } // all graphs start with these input events @@ -522,17 +474,14 @@ pub mod event { use ruma::{ events::{ - from_raw_json_value, pdu::{EventHash, Pdu}, room::member::{MemberEventContent, MembershipState}, - EventDeHelper, EventType, + EventType, }, - serde::CanonicalJsonValue, - signatures::reference_hash, EventId, RoomId, RoomVersionId, ServerName, UInt, UserId, }; - use serde::{de, ser, Deserialize, Serialize}; - use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; + use serde::{Deserialize, Serialize}; + use serde_json::Value as JsonValue; use state_res::Event; @@ -588,92 +537,10 @@ pub mod event { } #[derive(Clone, Debug, Deserialize, Serialize)] - struct EventIdHelper { - event_id: EventId, - } - - fn event_id(json: &RawJsonValue) -> Result { - use std::convert::TryFrom; - Ok(match from_raw_json_value::(&json) { - Ok(id) => id.event_id, - Err(_) => { - // panic!("NOT DURING TESTS"); - EventId::try_from(format!( - "${}", - reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) - .map_err(de::Error::custom)?, - )) - .map_err(de::Error::custom)? - } - }) - } - - // TODO: This no longer needs to be an enum now that PduStub is gone - #[derive(Clone, Debug)] - pub enum StateEvent { - Full(EventId, Pdu), - } - - impl Serialize for StateEvent { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - use ser::Error; - use std::convert::TryInto; - - match self { - Self::Full(id, ev) => { - // TODO: do we want to add the eventId when we - // serialize - let val: CanonicalJsonValue = serde_json::to_value(ev) - .map_err(S::Error::custom)? - .try_into() - .map_err(S::Error::custom)?; - - match val { - CanonicalJsonValue::Object(mut obj) => { - obj.insert( - "event_id".into(), - ruma::serde::to_canonical_value(id).map_err(S::Error::custom)?, - ); - obj.serialize(serializer) - } - _ => panic!("Pdu not an object"), - } - } - } - } - } - - impl<'de> de::Deserialize<'de> for StateEvent { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let json = Box::::deserialize(deserializer)?; - let EventDeHelper { - room_id, unsigned, .. - } = from_raw_json_value(&json)?; - - // TODO: do we even want to try for the existing ID - - // Determine whether the event is a full or stub - // based on the fields present. - Ok(if room_id.is_some() { - match unsigned { - Some(unsigned) if unsigned.redacted_because.is_some() => { - panic!("TODO deal with redacted events") - } - _ => StateEvent::Full( - event_id(&json)?, - Pdu::RoomV3Pdu(from_raw_json_value(&json)?), - ), - } - } else { - panic!("Found stub event") - }) - } + pub struct StateEvent { + pub event_id: EventId, + #[serde(flatten)] + pub rest: Pdu, } impl StateEvent { @@ -681,209 +548,168 @@ pub mod event { id: EventId, json: serde_json::Value, ) -> Result { - Ok(Self::Full( - id, - Pdu::RoomV3Pdu(serde_json::from_value(json)?), - )) + Ok(Self { + event_id: id, + rest: Pdu::RoomV3Pdu(serde_json::from_value(json)?), + }) } pub fn from_id_canon_obj( id: EventId, json: ruma::serde::CanonicalJsonObject, ) -> Result { - Ok(Self::Full( - id, + Ok(Self { + event_id: id, // TODO: this is unfortunate (from_value(to_value(json)))... - Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), - )) + rest: Pdu::RoomV3Pdu(serde_json::from_value(serde_json::to_value(json)?)?), + }) } pub fn is_power_event(&self) -> bool { - match self { - Self::Full(_, any_event) => match any_event { - Pdu::RoomV1Pdu(event) => match event.kind { - EventType::RoomPowerLevels - | EventType::RoomJoinRules - | EventType::RoomCreate => event.state_key == Some("".into()), - EventType::RoomMember => { - // TODO fix clone - if let Ok(content) = - serde_json::from_value::(event.content.clone()) + match &self.rest { + Pdu::RoomV1Pdu(event) => match event.kind { + EventType::RoomPowerLevels + | EventType::RoomJoinRules + | EventType::RoomCreate => event.state_key == Some("".into()), + EventType::RoomMember => { + // TODO fix clone + if let Ok(content) = + serde_json::from_value::(event.content.clone()) + { + if [MembershipState::Leave, MembershipState::Ban] + .contains(&content.membership) { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() + return event.sender.as_str() // TODO is None here a failure != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); - } } - - false } - _ => false, - }, - Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), + + false + } + _ => false, }, + Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), } } pub fn deserialize_content( &self, ) -> Result { - 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()), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()), + Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()), } } pub fn origin_server_ts(&self) -> &SystemTime { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, - Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, + Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, } } pub fn event_id(&self) -> &EventId { - match self { - // TODO; make this a &EventId - Self::Full(id, _) => id, - } + &self.event_id } pub fn sender(&self) -> &UserId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.sender, - Pdu::RoomV3Pdu(ev) => &ev.sender, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.sender, + Pdu::RoomV3Pdu(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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(), + Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(), } } pub fn room_id(&self) -> &RoomId { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.room_id, - Pdu::RoomV3Pdu(ev) => &ev.room_id, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.room_id, + Pdu::RoomV3Pdu(ev) => &ev.room_id, } } pub fn kind(&self) -> EventType { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.kind.clone(), - Pdu::RoomV3Pdu(ev) => ev.kind.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.kind.clone(), + Pdu::RoomV3Pdu(ev) => ev.kind.clone(), } } pub fn state_key(&self) -> String { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.state_key.clone().unwrap(), - Pdu::RoomV3Pdu(ev) => ev.state_key.clone().unwrap(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.state_key.clone().unwrap(), + Pdu::RoomV3Pdu(ev) => ev.state_key.clone().unwrap(), } } #[cfg(not(feature = "unstable-pre-spec"))] pub fn origin(&self) -> String { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.origin.clone(), - Pdu::RoomV3Pdu(ev) => ev.origin.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.origin.clone(), + Pdu::RoomV3Pdu(ev) => ev.origin.clone(), } } pub fn prev_event_ids(&self) -> Vec { - 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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), } } pub fn auth_events(&self) -> Vec { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => { - ev.auth_events.iter().map(|(id, _)| id).cloned().collect() - } - Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), + Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), } } 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(), - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => ev.content.clone(), + Pdu::RoomV3Pdu(ev) => ev.content.clone(), } } pub fn unsigned(&self) -> &BTreeMap { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.unsigned, - Pdu::RoomV3Pdu(ev) => &ev.unsigned, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.unsigned, + Pdu::RoomV3Pdu(ev) => &ev.unsigned, } } pub fn signatures( &self, ) -> BTreeMap, BTreeMap> { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, - Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), - }, + match &self.rest { + Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, + Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), } } pub fn hashes(&self) -> &EventHash { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.hashes, - Pdu::RoomV3Pdu(ev) => &ev.hashes, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.hashes, + Pdu::RoomV3Pdu(ev) => &ev.hashes, } } pub fn depth(&self) -> &UInt { - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(ev) => &ev.depth, - Pdu::RoomV3Pdu(ev) => &ev.depth, - }, + match &self.rest { + Pdu::RoomV1Pdu(ev) => &ev.depth, + Pdu::RoomV3Pdu(ev) => &ev.depth, } } pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { - 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) - } - }, + match &self.rest { + 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) + } } } @@ -893,54 +719,10 @@ pub mod event { /// version 3 and above. pub fn room_version(&self) -> RoomVersionId { // TODO: We have to know the actual room version this is not sufficient - match self { - Self::Full(_, ev) => match ev { - Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, - Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, - }, + match self.rest { + Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, + Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, } } } - - #[cfg(test)] - mod test { - use super::*; - - #[test] - fn deserialize_pdu() { - let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; - - let pdu = serde_json::from_str::(non_canonical_json).unwrap(); - - assert_eq!( - match &pdu { - StateEvent::Full(id, _) => id, - }, - &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") - ); - } - - #[test] - #[cfg_attr(not(feature = "unstable-pre-spec"), ignore)] - fn serialize_pdu() { - let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; - - let pdu = serde_json::from_str::(non_canonical_json).unwrap(); - - assert_eq!( - match &pdu { - StateEvent::Full(id, _) => id, - }, - &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") - ); - - // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing - // For testing we must serialize the PDU with the `event_id` field this is probably not correct for production - // although without them we get "Invalid bytes in DB" errors in conduit - assert_eq!( - ruma::serde::to_canonical_json_string(&pdu).unwrap(), - r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"event_id":"$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU","hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, - ) - } - } } From a7205c6ae7ff447bf4c3300e732d3fb1f180403d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Apr 2021 17:44:03 +0200 Subject: [PATCH 116/130] Clean up state_res_bench.rs --- benches/state_res_bench.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 99143284..66988f37 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -487,7 +487,7 @@ pub mod event { use ruma::{ events::{ pdu::{EventHash, Pdu}, - room::member::{MemberEventContent, MembershipState}, + room::member::MembershipState, EventType, }, EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId, @@ -509,6 +509,7 @@ pub mod event { fn sender(&self) -> &UserId { self.sender() } + fn kind(&self) -> EventType { self.kind() } @@ -516,6 +517,7 @@ pub mod event { fn content(&self) -> serde_json::Value { self.content() } + fn origin_server_ts(&self) -> SystemTime { *self.origin_server_ts() } @@ -523,24 +525,31 @@ pub mod event { fn state_key(&self) -> Option { self.state_key() } + fn prev_events(&self) -> Vec { self.prev_event_ids() } + fn depth(&self) -> &UInt { self.depth() } + fn auth_events(&self) -> Vec { self.auth_events() } + fn redacts(&self) -> Option<&EventId> { self.redacts() } + fn hashes(&self) -> &EventHash { self.hashes() } + fn signatures(&self) -> BTreeMap, BTreeMap> { self.signatures() } + fn unsigned(&self) -> &BTreeMap { self.unsigned() } @@ -583,25 +592,23 @@ pub mod event { | EventType::RoomCreate => event.state_key == Some("".into()), EventType::RoomMember => { // TODO fix clone - if let Ok(content) = - serde_json::from_value::(event.content.clone()) - { - if [MembershipState::Leave, MembershipState::Ban] - .contains(&content.membership) - { - return event.sender.as_str() - // TODO is None here a failure - != event.state_key.as_deref().unwrap_or("NOT A STATE KEY"); - } + if let Ok(membership) = serde_json::from_value::( + event.content["membership"].clone(), + ) { + [MembershipState::Leave, MembershipState::Ban].contains(&membership) + && event.sender.as_str() + // TODO is None here a failure + != event.state_key.as_deref().unwrap_or("NOT A STATE KEY") + } else { + false } - - false } _ => false, }, Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), } } + pub fn deserialize_content( &self, ) -> Result { @@ -610,12 +617,14 @@ pub mod event { Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()), } } + pub fn origin_server_ts(&self) -> &SystemTime { match &self.rest { Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, } } + pub fn event_id(&self) -> &EventId { &self.event_id } From ce665d213fffeaa47e146d01c6b87f9eb9feaa52 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Apr 2021 17:54:07 +0200 Subject: [PATCH 117/130] Bump ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 27ccb11a..1695a450 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "12ec0fb1680ebc4fec4fbefbbd0890ae4eaf3a88" +rev = "d27584ae3bdc035529e7389f1c392d4c96f9f8eb" features = ["events", "signatures"] [dev-dependencies] From d235957f947c73dee6e6f82346295ebf9eba2ec1 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Apr 2021 18:59:11 +0200 Subject: [PATCH 118/130] ci: Replace travis with GitHub actions --- .github/workflows/nightly.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/stable.yml | 28 ++++++++++++++++++++++++++++ .travis.yml | 7 ------- 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/nightly.yml create mode 100644 .github/workflows/stable.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..9c567b20 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,30 @@ +name: Rust Nightly + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + override: true + components: rustfmt, clippy + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check + - name: Catch common mistakes + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-features --all-targets -- -D warnings diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml new file mode 100644 index 00000000..2ebfed57 --- /dev/null +++ b/.github/workflows/stable.yml @@ -0,0 +1,28 @@ +name: Rust Stable + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests (unstable-pre-spec) + uses: actions-rs/cargo@v1 + with: + command: test + args: --features unstable-pre-spec diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index faafcf4f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: rust -sudo: false -rust: - - stable - - nightly -script: - - cargo test --features unstable-pre-spec From 72930a6d8891bebd2e399503fef766fe7a822731 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Apr 2021 18:59:26 +0200 Subject: [PATCH 119/130] Make README title bigger --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a547805d..ffc5542f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -### Matrix state resolution in rust! +# Matrix state resolution in Rust! ```rust /// Abstraction of a PDU so users can have their own PDU types. From 3bffb8ad82ad2425474b9ed24846c4840f5404f1 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Apr 2021 18:59:38 +0200 Subject: [PATCH 120/130] Title-case the README title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffc5542f..466bee4d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Matrix state resolution in Rust! +# Matrix State Resolution in Rust! ```rust /// Abstraction of a PDU so users can have their own PDU types. From ca01f334d7f32674f10e12dbf537d48304932578 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 26 Apr 2021 20:47:44 -0400 Subject: [PATCH 121/130] Create architecture document --- architecture.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 architecture.md diff --git a/architecture.md b/architecture.md new file mode 100644 index 00000000..633d9408 --- /dev/null +++ b/architecture.md @@ -0,0 +1,46 @@ +# Architecture + +This document describes the high-level architecture of state-res. +If you want to familiarize yourself with the code base, you are just in the right place! + +## Overview + +The state-res crate provides all the necessary algorithms to resolve the state of a room according to the Matrix spec. Given sets of state and the complete authorization chain, a final resolved state is calculated. + +The state sets (`BTreeMap<(EventType, StateKey), EventId>`) can be the state of a room according to different servers or at different points in time. The authorization chain is the recursive set of all events that authorize the following events. Any event that can be referenced needs to be available in the `event_map` argument, or the call fails. The `StateResolution` struct keeps no state and is only a collection of associated functions. + + +## Code Map + +This section talks briefly about important files and data structures. + +### `error` + +An enum representing all possible error cases in state-res. +Most of the variants are passing information of failures from other libraries except `Error::NotFound`. The `NotFound` variant is the error when an event used in state resolution was not in the `event_map`. + +### `event_auth` + +This module contains all the logic needed to authenticate and verify events. The main function for authentication is `auth_check`. There are a few checks that happen to every event and specific checks for some state events. + +**Note:** Any type of event can be check, not just state events. + + +### `room_version` + +`RoomVersion` holds information about each room version and is generated from `RoomVersionId`. During authentication, an event may be verified differently based on the room version. The `RoomVersion` keeps track of these differences. + +### `state_event` + +A trait `Event` that allows the state-res library to abstract over the type of an event. This avoids a lot of unnecessary conversions and gives more flexibility to users. + +### `lib` + +All the associated functions of `StateResolution` that are needed to resolve state live here. Everything that is used by `resolve` is exported so users have access to the algorithms. + +**Note:** only state events (events that have a state_key field) are allowed to participate in resolution. + + +## Testing + +state-res has three main test types, event sorting, event authentication, and state resolution. State resolution tests the whole system. Start by setting up a room with events and check the resolved state after adding conflicting events. Event authentication checks that an event passes or fails based on some initial state. Event sorting tests that given a DAG of events, the events can be predictably sorted. From d533c965028755728fdda32e88624f177ad256cd Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Mon, 26 Apr 2021 21:04:08 -0400 Subject: [PATCH 122/130] Add linebreaks at < 90, fix awkward phrasings --- architecture.md | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/architecture.md b/architecture.md index 633d9408..12993b99 100644 --- a/architecture.md +++ b/architecture.md @@ -5,10 +5,16 @@ If you want to familiarize yourself with the code base, you are just in the righ ## Overview -The state-res crate provides all the necessary algorithms to resolve the state of a room according to the Matrix spec. Given sets of state and the complete authorization chain, a final resolved state is calculated. - -The state sets (`BTreeMap<(EventType, StateKey), EventId>`) can be the state of a room according to different servers or at different points in time. The authorization chain is the recursive set of all events that authorize the following events. Any event that can be referenced needs to be available in the `event_map` argument, or the call fails. The `StateResolution` struct keeps no state and is only a collection of associated functions. +The state-res crate provides all the necessary algorithms to resolve the state of a +room according to the Matrix spec. Given sets of state and the complete authorization +chain, a final resolved state is calculated. +The state sets (`BTreeMap<(EventType, StateKey), EventId>`) can be the state of a room +according to different servers or at different points in time. The authorization chain +is the recursive set of all events that authorize events that come after. +Any event that can be referenced needs to be available in the `event_map` argument, +or the call fails. The `StateResolution` struct keeps no state and is only a +collection of associated functions. ## Code Map @@ -16,31 +22,46 @@ This section talks briefly about important files and data structures. ### `error` -An enum representing all possible error cases in state-res. -Most of the variants are passing information of failures from other libraries except `Error::NotFound`. The `NotFound` variant is the error when an event used in state resolution was not in the `event_map`. +An enum representing all possible error cases in state-res. Most of the variants are +passing information of failures from other libraries except `Error::NotFound`. +The `NotFound` variant is used whan an event was not in the `event_map`. ### `event_auth` -This module contains all the logic needed to authenticate and verify events. The main function for authentication is `auth_check`. There are a few checks that happen to every event and specific checks for some state events. +This module contains all the logic needed to authenticate and verify events. +The main function for authentication is `auth_check`. There are a few checks +that happen to every event and specific checks for some state events. +Each event is authenticated against the state before the event. +The state is built iterativly with each event being applied to the state and +the next checked before being added. **Note:** Any type of event can be check, not just state events. - ### `room_version` -`RoomVersion` holds information about each room version and is generated from `RoomVersionId`. During authentication, an event may be verified differently based on the room version. The `RoomVersion` keeps track of these differences. +`RoomVersion` holds information about each room version and is generated from +`RoomVersionId`. During authentication, an event may be verified differently based +on the room version. The `RoomVersion` keeps track of these differences. ### `state_event` -A trait `Event` that allows the state-res library to abstract over the type of an event. This avoids a lot of unnecessary conversions and gives more flexibility to users. +A trait `Event` that allows the state-res library to abstract over the type of an event. +This avoids a lot of unnecessary conversions and gives more flexibility to users. ### `lib` -All the associated functions of `StateResolution` that are needed to resolve state live here. Everything that is used by `resolve` is exported so users have access to the algorithms. +All the associated functions of `StateResolution` that are needed to resolve state live +here. Everything that is used by `resolve` is exported giving users access to the pieces +of the algorithm. -**Note:** only state events (events that have a state_key field) are allowed to participate in resolution. +**Note:** only state events (events that have a state_key field) are allowed to +participate in resolution. ## Testing -state-res has three main test types, event sorting, event authentication, and state resolution. State resolution tests the whole system. Start by setting up a room with events and check the resolved state after adding conflicting events. Event authentication checks that an event passes or fails based on some initial state. Event sorting tests that given a DAG of events, the events can be predictably sorted. +state-res has three main test types, event sorting, event authentication, and state +resolution. State resolution tests the whole system. Start by setting up a room with +events and check the resolved state after adding conflicting events. +Event authentication checks that an event passes or fails based on some initial state. +Event sorting tests that given a DAG of events, the events can be predictably sorted. From 29fd085de74f536df67971e131cad272358f8043 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Tue, 27 Apr 2021 15:39:20 -0400 Subject: [PATCH 123/130] Add more info to lib and state_event sections, fix english bugs --- architecture.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/architecture.md b/architecture.md index 12993b99..6dfd3c05 100644 --- a/architecture.md +++ b/architecture.md @@ -16,6 +16,13 @@ Any event that can be referenced needs to be available in the `event_map` argume or the call fails. The `StateResolution` struct keeps no state and is only a collection of associated functions. +## Important Terms + + - **event** In state-res this refers to a **P**ersistent **D**ata **U**nit which + represents the event and keeps metadata used for resolution + - **state resolution** The process of calculating the final state of a DAG from + conflicting input DAGs + ## Code Map This section talks briefly about important files and data structures. @@ -24,7 +31,7 @@ This section talks briefly about important files and data structures. An enum representing all possible error cases in state-res. Most of the variants are passing information of failures from other libraries except `Error::NotFound`. -The `NotFound` variant is used whan an event was not in the `event_map`. +The `NotFound` variant is used when an event was not in the `event_map`. ### `event_auth` @@ -32,8 +39,8 @@ This module contains all the logic needed to authenticate and verify events. The main function for authentication is `auth_check`. There are a few checks that happen to every event and specific checks for some state events. Each event is authenticated against the state before the event. -The state is built iterativly with each event being applied to the state and -the next checked before being added. +The state is built iteratively with each successive event being checked against +the current state then added. **Note:** Any type of event can be check, not just state events. @@ -45,22 +52,24 @@ on the room version. The `RoomVersion` keeps track of these differences. ### `state_event` -A trait `Event` that allows the state-res library to abstract over the type of an event. -This avoids a lot of unnecessary conversions and gives more flexibility to users. +A trait called `Event` that allows the state-res library to take any PDU type the user +supplies. The main `StateResolution::resolve` function can resolve any user-defined +type that satisfies `Event`. This avoids a lot of unnecessary conversions and +gives more flexibility to users. ### `lib` All the associated functions of `StateResolution` that are needed to resolve state live -here. Everything that is used by `resolve` is exported giving users access to the pieces -of the algorithm. +here. The focus is `StateResolution::resolve`, given a DAG and new events +`resolve` calculates the end state of the DAG. Everything that is used by `resolve` +is exported giving users access to the pieces of the algorithm. **Note:** only state events (events that have a state_key field) are allowed to participate in resolution. - ## Testing -state-res has three main test types, event sorting, event authentication, and state +state-res has three main test types: event sorting, event authentication, and state resolution. State resolution tests the whole system. Start by setting up a room with events and check the resolved state after adding conflicting events. Event authentication checks that an event passes or fails based on some initial state. From d359367c5d1ec4d0fcf21166d190016366173747 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 28 Apr 2021 06:33:40 -0400 Subject: [PATCH 124/130] Remove room_version section --- architecture.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/architecture.md b/architecture.md index 6dfd3c05..78280b09 100644 --- a/architecture.md +++ b/architecture.md @@ -44,12 +44,6 @@ the current state then added. **Note:** Any type of event can be check, not just state events. -### `room_version` - -`RoomVersion` holds information about each room version and is generated from -`RoomVersionId`. During authentication, an event may be verified differently based -on the room version. The `RoomVersion` keeps track of these differences. - ### `state_event` A trait called `Event` that allows the state-res library to take any PDU type the user From c20893e536bea4d17a9fe6af28428fb17169b56f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 29 Apr 2021 20:51:38 +0200 Subject: [PATCH 125/130] Bump ruma --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1695a450..79deb406 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unstable-pre-spec = ["ruma/unstable-pre-spec"] [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "d27584ae3bdc035529e7389f1c392d4c96f9f8eb" +rev = "8c286e78d41770fe431e7304cc2fe23e383793df" features = ["events", "signatures"] [dev-dependencies] From 138ecd4f354052bf2578eb72cbda34a4d2986ed1 Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 29 Apr 2021 12:06:50 -0400 Subject: [PATCH 126/130] Use the RoomVersion struct in event_auth --- src/error.rs | 14 ++++++-------- src/event_auth.rs | 19 ++++++++++--------- src/lib.rs | 10 +++++----- src/room_version.rs | 36 +++++++++++++++++++++++++++--------- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/error.rs b/src/error.rs index 51ca8fcc..1f27df89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,3 @@ -use std::num::ParseIntError; - use serde_json::Error as JsonError; use thiserror::Error; @@ -13,19 +11,19 @@ pub enum Error { #[error(transparent)] SerdeJson(#[from] JsonError), - /// An error that occurs when converting from JSON numbers to rust. - #[error(transparent)] - IntParseError(#[from] ParseIntError), + /// The given option or version is unsupported. + #[error("Unsupported room version: {0}")] + Unsupported(String), + /// The given event was not found. #[error("Not found error: {0}")] NotFound(String), + /// Invalid fields in the given PDU. #[error("Invalid PDU: {0}")] InvalidPdu(String), - #[error("Conversion failed: {0}")] - ConversionError(String), - + /// A custom error. #[error("{0}")] Custom(Box), } diff --git a/src/event_auth.rs b/src/event_auth.rs index 25aab27a..286e5d17 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -15,7 +15,7 @@ use ruma::{ RoomVersionId, UserId, }; -use crate::{Error, Event, Result, StateMap}; +use crate::{room_version::RoomVersion, Error, Event, Result, StateMap}; /// For the given event `kind` what are the relevant auth events /// that are needed to authenticate this `content`. @@ -82,7 +82,7 @@ pub fn auth_types_for_event( /// ## Returns /// This returns an `Error` only when serialization fails or some other fatal outcome. pub fn auth_check( - room_version: &RoomVersionId, + room_version: &RoomVersion, incoming_event: &Arc, prev_event: Option>, auth_events: &StateMap>, @@ -180,7 +180,7 @@ pub fn auth_check( // [synapse] checks for federation here // 4. if type is m.room.aliases - if incoming_event.kind() == EventType::RoomAliases && room_version < &RoomVersionId::Version6 { + if incoming_event.kind() == EventType::RoomAliases && room_version.special_case_aliases_auth { log::info!("starting m.room.aliases check"); // If sender's domain doesn't matches state_key, reject @@ -280,7 +280,7 @@ pub fn auth_check( // Servers should only apply redaction's to events where the sender's domains match, // or the sender of the redaction has the appropriate permissions per the power levels. - if room_version >= &RoomVersionId::Version3 + if room_version.extra_redaction_checks && incoming_event.kind() == EventType::RoomRedaction && !check_redaction(room_version, incoming_event, &auth_events)? { @@ -320,7 +320,7 @@ pub fn valid_membership_change( .map(|t| serde_json::from_value::(t.clone())); let target_user_id = - UserId::try_from(state_key).map_err(|e| Error::ConversionError(format!("{}", e)))?; + UserId::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{}", e)))?; let key = (EventType::RoomMember, user_sender.to_string()); let sender = auth_events.get(&key); @@ -538,7 +538,7 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>) /// Confirm that the event sender has the required power levels. pub fn check_power_levels( - room_version: &RoomVersionId, + room_version: &RoomVersion, power_event: &Arc, auth_events: &StateMap>, ) -> Option { @@ -639,7 +639,7 @@ pub fn check_power_levels( } // Notifications, currently there is only @room - if room_version >= &RoomVersionId::Version6 { + if room_version.limit_notifications_power_levels { let old_level = old_state.notifications.room; let new_level = new_state.notifications.room; if old_level != new_level { @@ -693,7 +693,7 @@ fn get_deserialize_levels( /// Does the event redacting come from a user with enough power to redact the given event. pub fn check_redaction( - _room_version: &RoomVersionId, + _room_version: &RoomVersion, redaction_event: &Arc, auth_events: &StateMap>, ) -> Result { @@ -705,7 +705,8 @@ pub fn check_redaction( return Ok(true); } - // If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow + // If the domain of the event_id of the event being redacted is the same as the + // domain of the event_id of the m.room.redaction, allow if redaction_event.event_id().server_name() == redaction_event .redacts() diff --git a/src/lib.rs b/src/lib.rs index 569b4575..69883e53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use std::{ }; use maplit::btreeset; +use room_version::RoomVersion; use ruma::{ events::{ room::{ @@ -21,12 +22,10 @@ mod error; pub mod event_auth; pub mod room_version; mod state_event; -mod state_store; pub use error::{Error, Result}; pub use event_auth::{auth_check, auth_types_for_event}; pub use state_event::Event; -pub use state_store::StateStore; /// A mapping of event type and state_key to some value `T`, usually an `EventId`. pub type StateMap = BTreeMap<(EventType, String), T>; @@ -111,10 +110,11 @@ impl StateResolution { log::debug!("SRTD {:?}", sorted_control_levels); + let room_version = RoomVersion::new(room_version)?; // sequentially auth check each control event. let resolved_control = StateResolution::iterative_auth_check( room_id, - room_version, + &room_version, &sorted_control_levels, &clean, event_map, @@ -166,7 +166,7 @@ impl StateResolution { let mut resolved_state = StateResolution::iterative_auth_check( room_id, - room_version, + &room_version, &sorted_left_events, &resolved_control, // The control events are added to the final resolved state event_map, @@ -436,7 +436,7 @@ impl StateResolution { /// function. pub fn iterative_auth_check( room_id: &RoomId, - room_version: &RoomVersionId, + room_version: &RoomVersion, events_to_check: &[EventId], unconflicted_state: &StateMap, event_map: &mut EventMap>, diff --git a/src/room_version.rs b/src/room_version.rs index a8db26ea..10e64639 100644 --- a/src/room_version.rs +++ b/src/room_version.rs @@ -1,5 +1,7 @@ use ruma::RoomVersionId; +use crate::{Error, Result}; + pub enum RoomDisposition { /// A room version that has a stable specification. Stable, @@ -36,29 +38,39 @@ pub struct RoomVersion { /// not sure pub enforce_key_validity: bool, - // bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules + // bool: before MSC2261/MSC2432, + /// `m.room.aliases` had special auth rules and redaction rules + /// before room version 6. 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 + /// 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. + /// Verify notifications key while checking m.room.power_levels. pub limit_notifications_power_levels: bool, + /// Extra rules when verifying redaction events. + pub extra_redaction_checks: bool, } impl RoomVersion { - pub fn new(version: &RoomVersionId) -> Self { - match version { + pub fn new(version: &RoomVersionId) -> Result { + Ok(match version { RoomVersionId::Version1 => Self::version_1(), RoomVersionId::Version2 => Self::version_2(), RoomVersionId::Version3 => Self::version_3(), RoomVersionId::Version4 => Self::version_4(), RoomVersionId::Version5 => Self::version_5(), RoomVersionId::Version6 => Self::version_6(), - _ => panic!("unspec'ed room version"), - } + ver => { + return Err(Error::Unsupported(format!( + "found version `{}`", + ver.as_str() + ))) + } + }) } fn version_1() -> Self { @@ -71,6 +83,7 @@ impl RoomVersion { special_case_aliases_auth: true, strict_canonicaljson: false, limit_notifications_power_levels: false, + extra_redaction_checks: false, } } @@ -84,6 +97,7 @@ impl RoomVersion { special_case_aliases_auth: true, strict_canonicaljson: false, limit_notifications_power_levels: false, + extra_redaction_checks: false, } } @@ -97,6 +111,7 @@ impl RoomVersion { special_case_aliases_auth: true, strict_canonicaljson: false, limit_notifications_power_levels: false, + extra_redaction_checks: true, } } @@ -110,6 +125,7 @@ impl RoomVersion { special_case_aliases_auth: true, strict_canonicaljson: false, limit_notifications_power_levels: false, + extra_redaction_checks: true, } } @@ -123,6 +139,7 @@ impl RoomVersion { special_case_aliases_auth: true, strict_canonicaljson: false, limit_notifications_power_levels: false, + extra_redaction_checks: true, } } @@ -136,6 +153,7 @@ impl RoomVersion { special_case_aliases_auth: false, strict_canonicaljson: true, limit_notifications_power_levels: true, + extra_redaction_checks: true, } } } From f62df4d9aec7b65075faa58a33d7d92d43a5dd5a Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 29 Apr 2021 12:07:35 -0400 Subject: [PATCH 127/130] Remove StateStore trait and clean up imports in event_auth --- benches/state_res_bench.rs | 73 +++++++++++++++++++++++++++-- src/event_auth.rs | 95 +++++++++++++++++--------------------- tests/event_sorting.rs | 4 +- tests/res_with_auth_ids.rs | 2 +- tests/state_res.rs | 2 +- tests/utils.rs | 73 +++++++++++++++++++++++++++-- 6 files changed, 184 insertions(+), 65 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 66988f37..34e41043 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -5,7 +5,7 @@ // To pass args to criterion, use this form // `cargo bench --bench -- --save-baseline `. use std::{ - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, convert::TryFrom, sync::Arc, time::{Duration, UNIX_EPOCH}, @@ -26,7 +26,7 @@ use ruma::{ EventId, RoomId, RoomVersionId, UserId, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; +use state_res::{Error, Event, Result, StateMap, StateResolution}; static mut SERVER_TIMESTAMP: u64 = 0; @@ -153,13 +153,78 @@ criterion_main!(benches); pub struct TestStore(pub BTreeMap>); #[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { +impl TestStore { + pub fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } + + /// Returns the events that correspond to the `event_ids` sorted in the same order. + pub fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { + let mut events = vec![]; + for id in event_ids { + events.push(self.get_event(room_id, id)?); + } + Ok(events) + } + + /// Returns a Vec of the related auth events to the given `event`. + pub fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(room_id, &ev_id)?; + + stack.extend(event.auth_events().clone()); + } + + Ok(result) + } + + /// Returns a Vec representing the difference in auth chains of the given `events`. + pub fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: Vec>, + ) -> Result> { + let mut chains = vec![]; + for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } } impl TestStore { diff --git a/src/event_auth.rs b/src/event_auth.rs index 286e5d17..520f5cff 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -5,10 +5,10 @@ use maplit::btreeset; use ruma::{ events::{ room::{ - self, - join_rules::JoinRule, - member::{self, MembershipState}, - power_levels::{self, PowerLevelsEventContent}, + create::CreateEventContent, + join_rules::{JoinRule, JoinRulesEventContent}, + member::{MembershipState, ThirdPartyInvite}, + power_levels::PowerLevelsEventContent, }, EventType, }, @@ -39,7 +39,7 @@ pub fn auth_types_for_event( if let Some(state_key) = state_key { if let Some(Ok(membership)) = content .get("membership") - .map(|m| serde_json::from_value::(m.clone())) + .map(|m| serde_json::from_value::(m.clone())) { if [MembershipState::Join, MembershipState::Invite].contains(&membership) { let key = (EventType::RoomJoinRules, "".to_string()); @@ -54,9 +54,10 @@ pub fn auth_types_for_event( } if membership == MembershipState::Invite { - if let Some(Ok(t_id)) = content.get("third_party_invite").map(|t| { - serde_json::from_value::(t.clone()) - }) { + if let Some(Ok(t_id)) = content + .get("third_party_invite") + .map(|t| serde_json::from_value::(t.clone())) + { let key = (EventType::RoomThirdPartyInvite, t_id.signed.token); if !auth_types.contains(&key) { auth_types.push(key) @@ -206,7 +207,7 @@ pub fn auth_check( let membership = incoming_event .content() .get("membership") - .map(|m| serde_json::from_value::(m.clone())); + .map(|m| serde_json::from_value::(m.clone())); if !matches!(membership, Some(Ok(_))) { log::warn!("no valid membership field found for m.room.member event content"); @@ -308,7 +309,7 @@ pub fn valid_membership_change( current_third_party_invite: Option>, auth_events: &StateMap>, ) -> Result { - let target_membership = serde_json::from_value::( + let target_membership = serde_json::from_value::( content .get("membership") .expect("we should test before that this field exists") @@ -317,39 +318,37 @@ pub fn valid_membership_change( let third_party_invite = content .get("third_party_invite") - .map(|t| serde_json::from_value::(t.clone())); + .map(|t| serde_json::from_value::(t.clone())); let target_user_id = UserId::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{}", e)))?; let key = (EventType::RoomMember, user_sender.to_string()); let sender = auth_events.get(&key); - let sender_membership = - sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok(serde_json::from_value::( - pdu.content() - .get("membership") - .expect("we assume existing events are valid") - .clone(), - )?) - })?; + let sender_membership = sender.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| { + Ok(serde_json::from_value::( + pdu.content() + .get("membership") + .expect("we assume existing events are valid") + .clone(), + )?) + })?; let key = (EventType::RoomMember, target_user_id.to_string()); let current = auth_events.get(&key); - let current_membership = - current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok(serde_json::from_value::( - pdu.content() - .get("membership") - .expect("we assume existing events are valid") - .clone(), - )?) - })?; + let current_membership = current.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| { + Ok(serde_json::from_value::( + pdu.content() + .get("membership") + .expect("we assume existing events are valid") + .clone(), + )?) + })?; let key = (EventType::RoomPowerLevels, "".into()); let power_levels = auth_events.get(&key).map_or_else( - || Ok::<_, Error>(power_levels::PowerLevelsEventContent::default()), + || Ok::<_, Error>(PowerLevelsEventContent::default()), |power_levels| { serde_json::from_value::(power_levels.content()) .map_err(Into::into) @@ -358,7 +357,7 @@ pub fn valid_membership_change( let sender_power = power_levels.users.get(user_sender).map_or_else( || { - if sender_membership != member::MembershipState::Join { + if sender_membership != MembershipState::Join { None } else { Some(&power_levels.users_default) @@ -369,7 +368,7 @@ pub fn valid_membership_change( ); let target_power = power_levels.users.get(&target_user_id).map_or_else( || { - if target_membership != member::MembershipState::Join { + if target_membership != MembershipState::Join { None } else { Some(&power_levels.users_default) @@ -383,9 +382,7 @@ pub fn valid_membership_change( let join_rules_event = auth_events.get(&key); let mut join_rules = JoinRule::Invite; if let Some(jr) = join_rules_event { - join_rules = - serde_json::from_value::(jr.content())? - .join_rule; + join_rules = serde_json::from_value::(jr.content())?.join_rule; } if let Some(prev) = prev_event { @@ -494,7 +491,7 @@ pub fn check_event_sender_in_room( ) -> Option { let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; - let membership = serde_json::from_value::( + let membership = serde_json::from_value::( mem.content() .get("membership") .expect("we should test before that this field exists") @@ -555,15 +552,11 @@ pub fn check_power_levels( // If users key in content is not a dictionary with keys that are valid user IDs // with values that are integers (or a string that is an integer), reject. - let user_content = serde_json::from_value::( - power_event.content(), - ) - .unwrap(); + let user_content = + serde_json::from_value::(power_event.content()).unwrap(); - let current_content = serde_json::from_value::( - current_state.content(), - ) - .unwrap(); + let current_content = + serde_json::from_value::(current_state.content()).unwrap(); // validation of users is done in Ruma, synapse for loops validating user_ids and integers here log::info!("validation of power event finished"); @@ -728,7 +721,7 @@ pub fn check_membership(member_event: Option>, state: Membershi if let Some(Ok(membership)) = event .content() .get("membership") - .map(|m| serde_json::from_value::(m.clone())) + .map(|m| serde_json::from_value::(m.clone())) { membership == state } else { @@ -773,9 +766,7 @@ pub fn get_named_level(auth_events: &StateMap>, name: &str, def /// object. pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap>) -> i64 { if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { - if let Ok(content) = - serde_json::from_value::(pl.content()) - { + if let Ok(content) = serde_json::from_value::(pl.content()) { if let Some(level) = content.users.get(user_id) { (*level).into() } else { @@ -788,9 +779,7 @@ pub fn get_user_power_level(user_id: &UserId, auth_events: &StateMap(create.content()) - { + if let Ok(c) = serde_json::from_value::(create.content()) { if &c.creator == user_id { 100 } else { @@ -815,7 +804,7 @@ pub fn get_send_level( log::debug!("{:?} {:?}", e_type, state_key); power_lvl .and_then(|ple| { - serde_json::from_value::(ple.content()) + serde_json::from_value::(ple.content()) .map(|content| { content.events.get(&e_type).cloned().unwrap_or_else(|| { if state_key.is_some() { @@ -853,7 +842,7 @@ pub fn can_send_invite(event: &Arc, auth_events: &StateMap>) pub fn verify_third_party_invite( user_state_key: Option<&str>, sender: &UserId, - tp_id: &member::ThirdPartyInvite, + tp_id: &ThirdPartyInvite, current_third_party_invite: Option>, ) -> bool { // 1. check for user being banned happens before this is called diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 81e7aefd..88a79140 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use ruma::{events::EventType, EventId, RoomVersionId}; -use state_res::{is_power_event, StateMap}; +use state_res::{is_power_event, room_version::RoomVersion, StateMap}; mod utils; use utils::{room_id, INITIAL_EVENTS}; @@ -46,7 +46,7 @@ fn test_event_sort() { // TODO we may be able to skip this since they are resolved according to spec let resolved_power = state_res::StateResolution::iterative_auth_check( &room_id(), - &RoomVersionId::Version6, + &RoomVersion::new(&RoomVersionId::Version6).unwrap(), &sorted_power_events, &BTreeMap::new(), // unconflicted events &mut events, diff --git a/tests/res_with_auth_ids.rs b/tests/res_with_auth_ids.rs index 24af024f..4c637abe 100644 --- a/tests/res_with_auth_ids.rs +++ b/tests/res_with_auth_ids.rs @@ -4,7 +4,7 @@ use std::{collections::BTreeMap, sync::Arc}; use ruma::{events::EventType, EventId, RoomVersionId}; use serde_json::json; -use state_res::{EventMap, StateMap, StateResolution, StateStore}; +use state_res::{EventMap, StateMap, StateResolution}; mod utils; use utils::{ diff --git a/tests/state_res.rs b/tests/state_res.rs index 36307926..11452f53 100644 --- a/tests/state_res.rs +++ b/tests/state_res.rs @@ -6,7 +6,7 @@ use ruma::{ EventId, RoomVersionId, }; use serde_json::json; -use state_res::{StateMap, StateResolution, StateStore}; +use state_res::{StateMap, StateResolution}; use tracing_subscriber as tracer; mod utils; diff --git a/tests/utils.rs b/tests/utils.rs index c3d81959..499a23fb 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,7 +1,7 @@ #![allow(clippy::or_fun_call, clippy::expect_fun_call, dead_code)] use std::{ - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, convert::TryFrom, sync::{Arc, Once}, time::{Duration, UNIX_EPOCH}, @@ -20,7 +20,7 @@ use ruma::{ EventId, RoomId, RoomVersionId, UserId, }; use serde_json::{json, Value as JsonValue}; -use state_res::{Error, Event, Result, StateMap, StateResolution, StateStore}; +use state_res::{Error, Event, Result, StateMap, StateResolution}; use tracing_subscriber as tracer; pub use event::StateEvent; @@ -215,13 +215,78 @@ pub fn do_check( pub struct TestStore(pub BTreeMap>); #[allow(unused)] -impl StateStore for TestStore { - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { +impl TestStore { + pub fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result> { self.0 .get(event_id) .map(Arc::clone) .ok_or_else(|| Error::NotFound(format!("{} not found", event_id.to_string()))) } + + /// Returns the events that correspond to the `event_ids` sorted in the same order. + pub fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { + let mut events = vec![]; + for id in event_ids { + events.push(self.get_event(room_id, id)?); + } + Ok(events) + } + + /// Returns a Vec of the related auth events to the given `event`. + pub fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { + let mut result = vec![]; + let mut stack = event_ids.to_vec(); + + // DFS for auth event chain + while !stack.is_empty() { + let ev_id = stack.pop().unwrap(); + if result.contains(&ev_id) { + continue; + } + + result.push(ev_id.clone()); + + let event = self.get_event(room_id, &ev_id)?; + + stack.extend(event.auth_events().clone()); + } + + Ok(result) + } + + /// Returns a Vec representing the difference in auth chains of the given `events`. + pub fn auth_chain_diff( + &self, + room_id: &RoomId, + event_ids: Vec>, + ) -> Result> { + let mut chains = vec![]; + for ids in event_ids { + // TODO state store `auth_event_ids` returns self in the event ids list + // when an event returns `auth_event_ids` self is not contained + let chain = self + .auth_event_ids(room_id, &ids)? + .into_iter() + .collect::>(); + chains.push(chain); + } + + if let Some(chain) = chains.first() { + let rest = chains.iter().skip(1).flatten().cloned().collect(); + let common = chain.intersection(&rest).collect::>(); + + Ok(chains + .iter() + .flatten() + .filter(|id| !common.contains(&id)) + .cloned() + .collect::>() + .into_iter() + .collect()) + } else { + Ok(vec![]) + } + } } pub fn event_id(id: &str) -> EventId { From 41b8c14d6e1a10423fc08e0a668ffe909ac3107f Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 29 Apr 2021 13:11:47 -0400 Subject: [PATCH 128/130] Remove state_store module --- src/state_store.rs | 76 ---------------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 src/state_store.rs diff --git a/src/state_store.rs b/src/state_store.rs deleted file mode 100644 index b8a1ea88..00000000 --- a/src/state_store.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{collections::BTreeSet, sync::Arc}; - -use ruma::{EventId, RoomId}; - -use crate::{Event, Result}; - -/// TODO: this is only used in testing on this branch now REMOVE -pub trait StateStore { - /// Return a single event based on the EventId. - fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result>; - - /// Returns the events that correspond to the `event_ids` sorted in the same order. - fn get_events(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result>> { - let mut events = vec![]; - for id in event_ids { - events.push(self.get_event(room_id, id)?); - } - Ok(events) - } - - /// Returns a Vec of the related auth events to the given `event`. - fn auth_event_ids(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result> { - let mut result = vec![]; - let mut stack = event_ids.to_vec(); - - // DFS for auth event chain - while !stack.is_empty() { - let ev_id = stack.pop().unwrap(); - if result.contains(&ev_id) { - continue; - } - - result.push(ev_id.clone()); - - let event = self.get_event(room_id, &ev_id)?; - - stack.extend(event.auth_events().clone()); - } - - Ok(result) - } - - /// Returns a Vec representing the difference in auth chains of the given `events`. - fn auth_chain_diff( - &self, - room_id: &RoomId, - event_ids: Vec>, - ) -> Result> { - let mut chains = vec![]; - for ids in event_ids { - // TODO state store `auth_event_ids` returns self in the event ids list - // when an event returns `auth_event_ids` self is not contained - let chain = self - .auth_event_ids(room_id, &ids)? - .into_iter() - .collect::>(); - chains.push(chain); - } - - if let Some(chain) = chains.first() { - let rest = chains.iter().skip(1).flatten().cloned().collect(); - let common = chain.intersection(&rest).collect::>(); - - Ok(chains - .iter() - .flatten() - .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() - .collect()) - } else { - Ok(vec![]) - } - } -} From 509283d947199af22e5b4ed22cf928482ab105ef Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Thu, 29 Apr 2021 13:49:04 -0400 Subject: [PATCH 129/130] Remove so many allocations for auth chain, address review --- src/lib.rs | 10 +++++----- src/room_version.rs | 19 ++++++++++--------- tests/event_sorting.rs | 4 ++-- tests/utils.rs | 9 ++++----- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 69883e53..cacd03a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,6 +226,8 @@ impl StateResolution { _room_id: &RoomId, auth_event_ids: &[Vec], ) -> Result> { + use itertools::Itertools; + let mut chains = vec![]; for ids in auth_event_ids { @@ -235,17 +237,15 @@ impl StateResolution { chains.push(chain); } - if let Some(chain) = chains.first() { + if let Some(chain) = chains.first().cloned() { let rest = chains.iter().skip(1).flatten().cloned().collect(); let common = chain.intersection(&rest).collect::>(); Ok(chains - .iter() + .into_iter() .flatten() .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() + .dedup() .collect()) } else { Ok(vec![]) diff --git a/src/room_version.rs b/src/room_version.rs index 10e64639..a4c27087 100644 --- a/src/room_version.rs +++ b/src/room_version.rs @@ -38,18 +38,19 @@ pub struct RoomVersion { /// not sure pub enforce_key_validity: bool, - // bool: before MSC2261/MSC2432, /// `m.room.aliases` had special auth rules and redaction rules /// before room version 6. + /// + /// before MSC2261/MSC2432, 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. /// Verify notifications key while checking m.room.power_levels. + /// + /// bool: MSC2209: Check 'notifications' pub limit_notifications_power_levels: bool, /// Extra rules when verifying redaction events. pub extra_redaction_checks: bool, @@ -73,7 +74,7 @@ impl RoomVersion { }) } - fn version_1() -> Self { + pub fn version_1() -> Self { Self { version: RoomVersionId::Version1, disposition: RoomDisposition::Stable, @@ -87,7 +88,7 @@ impl RoomVersion { } } - fn version_2() -> Self { + pub fn version_2() -> Self { Self { version: RoomVersionId::Version2, disposition: RoomDisposition::Stable, @@ -101,7 +102,7 @@ impl RoomVersion { } } - fn version_3() -> Self { + pub fn version_3() -> Self { Self { version: RoomVersionId::Version3, disposition: RoomDisposition::Stable, @@ -115,7 +116,7 @@ impl RoomVersion { } } - fn version_4() -> Self { + pub fn version_4() -> Self { Self { version: RoomVersionId::Version4, disposition: RoomDisposition::Stable, @@ -129,7 +130,7 @@ impl RoomVersion { } } - fn version_5() -> Self { + pub fn version_5() -> Self { Self { version: RoomVersionId::Version5, disposition: RoomDisposition::Stable, @@ -143,7 +144,7 @@ impl RoomVersion { } } - fn version_6() -> Self { + pub fn version_6() -> Self { Self { version: RoomVersionId::Version6, disposition: RoomDisposition::Stable, diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 88a79140..67270e52 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use ruma::{events::EventType, EventId, RoomVersionId}; +use ruma::{events::EventType, EventId}; use state_res::{is_power_event, room_version::RoomVersion, StateMap}; mod utils; @@ -46,7 +46,7 @@ fn test_event_sort() { // TODO we may be able to skip this since they are resolved according to spec let resolved_power = state_res::StateResolution::iterative_auth_check( &room_id(), - &RoomVersion::new(&RoomVersionId::Version6).unwrap(), + &RoomVersion::version_6(), &sorted_power_events, &BTreeMap::new(), // unconflicted events &mut events, diff --git a/tests/utils.rs b/tests/utils.rs index 499a23fb..d487749e 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -260,6 +260,7 @@ impl TestStore { room_id: &RoomId, event_ids: Vec>, ) -> Result> { + use itertools::Itertools; let mut chains = vec![]; for ids in event_ids { // TODO state store `auth_event_ids` returns self in the event ids list @@ -271,17 +272,15 @@ impl TestStore { chains.push(chain); } - if let Some(chain) = chains.first() { + if let Some(chain) = chains.first().cloned() { let rest = chains.iter().skip(1).flatten().cloned().collect(); let common = chain.intersection(&rest).collect::>(); Ok(chains - .iter() + .into_iter() .flatten() .filter(|id| !common.contains(&id)) - .cloned() - .collect::>() - .into_iter() + .dedup() .collect()) } else { Ok(vec![]) From 56bf45c0235701ac6df56993c327d2f97a499ef9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 May 2021 00:03:08 +0200 Subject: [PATCH 130/130] Fix default-allowed clippy lints --- benches/state_res_bench.rs | 16 ++++++++-------- src/event_auth.rs | 24 ++++++++++++------------ src/lib.rs | 31 +++++++++++++++---------------- tests/event_auth.rs | 12 ++++-------- tests/event_sorting.rs | 4 ++-- 5 files changed, 41 insertions(+), 46 deletions(-) diff --git a/benches/state_res_bench.rs b/benches/state_res_bench.rs index 34e41043..1937d65b 100644 --- a/benches/state_res_bench.rs +++ b/benches/state_res_bench.rs @@ -55,9 +55,9 @@ fn resolution_shallow_auth_chain(c: &mut Criterion) { let (state_at_bob, state_at_charlie, _) = store.set_up(); b.iter(|| { - let mut ev_map: state_res::EventMap> = store.0.clone(); + let mut ev_map: state_res::EventMap> = store.0.clone(); let state_sets = vec![state_at_bob.clone(), state_at_charlie.clone()]; - let _ = match StateResolution::resolve::( + let _ = match StateResolution::resolve::( &room_id(), &RoomVersionId::Version2, &state_sets, @@ -115,7 +115,7 @@ fn resolve_deeper_event_set(c: &mut Criterion) { b.iter(|| { let state_sets = vec![state_set_a.clone(), state_set_b.clone()]; - let _ = match StateResolution::resolve::( + let _ = match StateResolution::resolve::( &room_id(), &RoomVersionId::Version2, &state_sets, @@ -216,7 +216,7 @@ impl TestStore { Ok(chains .iter() .flatten() - .filter(|id| !common.contains(&id)) + .filter(|id| !common.contains(id)) .cloned() .collect::>() .into_iter() @@ -227,7 +227,7 @@ impl TestStore { } } -impl TestStore { +impl TestStore { pub fn set_up(&mut self) -> (StateMap, StateMap, StateMap) { let create_event = to_pdu_event::( "CREATE", @@ -420,7 +420,7 @@ where // all graphs start with these input events #[allow(non_snake_case)] -fn INITIAL_EVENTS() -> BTreeMap> { +fn INITIAL_EVENTS() -> BTreeMap> { vec![ to_pdu_event::( "CREATE", @@ -502,7 +502,7 @@ fn INITIAL_EVENTS() -> BTreeMap> { // all graphs start with these input events #[allow(non_snake_case)] -fn BAN_STATE_SET() -> BTreeMap> { +fn BAN_STATE_SET() -> BTreeMap> { vec![ to_pdu_event( "PA", @@ -765,7 +765,7 @@ pub mod event { pub fn signatures( &self, - ) -> BTreeMap, BTreeMap> { + ) -> BTreeMap, BTreeMap> { match &self.rest { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), diff --git a/src/event_auth.rs b/src/event_auth.rs index 520f5cff..bad99240 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -220,7 +220,7 @@ pub fn auth_check( incoming_event.content(), prev_event, current_third_party_invite, - &auth_events, + auth_events, )? { return Ok(false); } @@ -230,7 +230,7 @@ pub fn auth_check( } // If the sender's current membership state is not join, reject - match check_event_sender_in_room(&incoming_event.sender(), &auth_events) { + match check_event_sender_in_room(incoming_event.sender(), auth_events) { Some(true) => {} // sender in room Some(false) => { log::warn!("sender's membership is not join"); @@ -245,7 +245,7 @@ pub fn auth_check( // Allow if and only if sender's current power level is greater than // or equal to the invite level if incoming_event.kind() == EventType::RoomThirdPartyInvite - && !can_send_invite(incoming_event, &auth_events)? + && !can_send_invite(incoming_event, auth_events)? { log::warn!("sender's cannot send invites in this room"); return Ok(false); @@ -253,7 +253,7 @@ pub fn auth_check( // If the event type's required power level is greater than the sender's power level, reject // If the event has a state_key that starts with an @ and does not match the sender, reject. - if !can_send_event(&incoming_event, &auth_events) { + if !can_send_event(incoming_event, auth_events) { log::warn!("user cannot send event"); return Ok(false); } @@ -262,7 +262,7 @@ pub fn auth_check( log::info!("starting m.room.power_levels check"); if let Some(required_pwr_lvl) = - check_power_levels(room_version, &incoming_event, &auth_events) + check_power_levels(room_version, incoming_event, auth_events) { if !required_pwr_lvl { log::warn!("power level was not allowed"); @@ -283,7 +283,7 @@ pub fn auth_check( if room_version.extra_redaction_checks && incoming_event.kind() == EventType::RoomRedaction - && !check_redaction(room_version, incoming_event, &auth_events)? + && !check_redaction(room_version, incoming_event, auth_events)? { return Ok(false); } @@ -405,7 +405,7 @@ pub fn valid_membership_change( || join_rules == JoinRule::Public; if !allow { - warn!("Can't join if join rules is not public and user is not invited/joined") + warn!("Can't join if join rules is not public and user is not invited/joined"); } allow } @@ -508,7 +508,7 @@ pub fn can_send_event(event: &Arc, auth_events: &StateMap>) let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple); - let user_level = get_user_power_level(&event.sender(), auth_events); + let user_level = get_user_power_level(event.sender(), auth_events); log::debug!( "{} ev_type {} usr {}", @@ -561,7 +561,7 @@ pub fn check_power_levels( // validation of users is done in Ruma, synapse for loops validating user_ids and integers here log::info!("validation of power event finished"); - let user_level = get_user_power_level(&power_event.sender(), auth_events); + let user_level = get_user_power_level(power_event.sender(), auth_events); let mut user_levels_to_check = btreeset![]; let old_list = ¤t_content.users; @@ -690,7 +690,7 @@ pub fn check_redaction( redaction_event: &Arc, auth_events: &StateMap>, ) -> Result { - let user_level = get_user_power_level(&redaction_event.sender(), auth_events); + 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 { @@ -806,7 +806,7 @@ pub fn get_send_level( .and_then(|ple| { serde_json::from_value::(ple.content()) .map(|content| { - content.events.get(&e_type).cloned().unwrap_or_else(|| { + content.events.get(e_type).copied().unwrap_or_else(|| { if state_key.is_some() { content.state_default } else { @@ -822,7 +822,7 @@ pub fn get_send_level( /// Check user can send invite. pub fn can_send_invite(event: &Arc, auth_events: &StateMap>) -> Result { - let user_level = get_user_power_level(&event.sender(), auth_events); + let user_level = get_user_power_level(event.sender(), auth_events); let key = (EventType::RoomPowerLevels, "".into()); let invite_level = auth_events .get(&key) diff --git a/src/lib.rs b/src/lib.rs index cacd03a0..84634e5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ impl StateResolution { log::info!("State resolution starting"); // split non-conflicting and conflicting state - let (clean, conflicting) = StateResolution::separate(&state_sets); + let (clean, conflicting) = StateResolution::separate(state_sets); log::info!("non conflicting {:?}", clean.len()); @@ -96,7 +96,7 @@ impl StateResolution { // get only the control events with a state_key: "" or ban/kick event (sender != state_key) let control_events = all_conflicted .iter() - .filter(|id| is_power_event_id(id, &event_map)) + .filter(|id| is_power_event_id(id, event_map)) .cloned() .collect::>(); @@ -281,7 +281,7 @@ impl StateResolution { // this is used in the `key_fn` passed to the lexico_topo_sort fn let mut event_to_pl = BTreeMap::new(); for event_id in graph.keys() { - let pl = StateResolution::get_power_level_for_sender(room_id, &event_id, event_map); + let pl = StateResolution::get_power_level_for_sender(room_id, event_id, event_map); log::info!("{} power level {}", event_id.to_string(), pl); event_to_pl.insert(event_id.clone(), pl); @@ -408,12 +408,11 @@ impl StateResolution { return 0; } - if let Some(content) = pl - .map(|pl| serde_json::from_value::(pl.content()).ok()) - .flatten() + if let Some(content) = + pl.and_then(|pl| serde_json::from_value::(pl.content()).ok()) { if let Ok(ev) = event { - if let Some(user) = content.users.get(&ev.sender()) { + if let Some(user) = content.users.get(ev.sender()) { log::debug!("found {} at power_level {}", ev.sender().as_str(), user); return (*user).into(); } @@ -461,7 +460,7 @@ impl StateResolution { let mut auth_events = BTreeMap::new(); for aid in &event.auth_events() { - if let Ok(ev) = StateResolution::get_or_load_event(room_id, &aid, event_map) { + if let Ok(ev) = StateResolution::get_or_load_event(room_id, aid, event_map) { // TODO synapse check "rejected_reason", I'm guessing this is redacted_because in ruma ?? auth_events.insert( ( @@ -477,9 +476,9 @@ impl StateResolution { } } - for key in event_auth::auth_types_for_event( + for key in auth_types_for_event( &event.kind(), - &event.sender(), + event.sender(), Some(state_key.clone()), event.content(), ) { @@ -510,7 +509,7 @@ impl StateResolution { } }); - if event_auth::auth_check( + if auth_check( room_version, &event, most_recent_prev_event, @@ -563,7 +562,7 @@ impl StateResolution { let auth_events = &event.auth_events(); pl = None; for aid in auth_events { - let ev = StateResolution::get_or_load_event(room_id, &aid, event_map).unwrap(); + let ev = StateResolution::get_or_load_event(room_id, aid, event_map).unwrap(); if is_type_and_key(&ev, EventType::RoomPowerLevels, "") { pl = Some(aid.clone()); break; @@ -625,7 +624,7 @@ impl StateResolution { while let Some(sort_ev) = event { log::debug!("mainline event_id {}", sort_ev.event_id().to_string()); let id = &sort_ev.event_id(); - if let Some(depth) = mainline_map.get(&id) { + if let Some(depth) = mainline_map.get(id) { return Ok(*depth); } @@ -634,7 +633,7 @@ impl StateResolution { event = None; for aid in auth_events { // dbg!(&aid); - let aev = StateResolution::get_or_load_event(room_id, &aid, event_map)?; + let aev = StateResolution::get_or_load_event(room_id, aid, event_map)?; if is_type_and_key(&aev, EventType::RoomPowerLevels, "") { event = Some(aev); break; @@ -663,8 +662,8 @@ impl StateResolution { .unwrap() .auth_events() { - if auth_diff.contains(&aid) { - if !graph.contains_key(&aid) { + if auth_diff.contains(aid) { + if !graph.contains_key(aid) { state.push(aid.clone()); } diff --git a/tests/event_auth.rs b/tests/event_auth.rs index 46e14758..bd91d0ab 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -1,13 +1,9 @@ use std::sync::Arc; -#[rustfmt::skip] // this deletes the comments for some reason yay! -use state_res::{ - event_auth::{ - // auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, - valid_membership_change, - }, - StateMap -}; +use state_res::{event_auth::valid_membership_change, StateMap}; +// use state_res::event_auth:::{ +// auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction, +// }; mod utils; use utils::{alice, charlie, event_id, member_content_ban, to_pdu_event, INITIAL_EVENTS}; diff --git a/tests/event_sorting.rs b/tests/event_sorting.rs index 67270e52..2f50fff5 100644 --- a/tests/event_sorting.rs +++ b/tests/event_sorting.rs @@ -28,7 +28,7 @@ fn test_event_sort() { let power_events = event_map .values() - .filter(|pdu| is_power_event(&pdu)) + .filter(|pdu| is_power_event(pdu)) .map(|pdu| pdu.event_id().clone()) .collect::>(); @@ -39,7 +39,7 @@ fn test_event_sort() { &room_id(), &power_events, &mut events, - &auth_chain, + auth_chain, ); // This is a TODO in conduit