mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-15 13:16:56 +00:00
refactor(wip): Replace local state resolution with upstream Ruma's
This commit is contained in:
24 files changed
+233
-5780
No files matched your search
@@ -130,7 +130,7 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
SignatureVerification(#[from] ruma::signatures::VerificationError),
|
||||
#[error(transparent)]
|
||||
StateRes(#[from] crate::state_res::Error),
|
||||
StateRes(#[from] ruma::state_res::Error),
|
||||
#[error("uiaa")]
|
||||
Uiaa(ruma::api::client::uiaa::UiaaInfo),
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
pub mod event;
|
||||
pub mod pdu;
|
||||
pub mod state_key;
|
||||
pub mod state_res;
|
||||
pub mod versions;
|
||||
|
||||
pub use event::{Event, TypeExt as EventTypeExt};
|
||||
pub use pdu::{PartialPdu, Pdu, PduCount, PduEvent, PduId, RawPduId, ShortId};
|
||||
pub use state_key::StateKey;
|
||||
pub use state_res::{StateMap, TypeStateKey};
|
||||
@@ -73,6 +73,9 @@ pub struct Pdu {
|
||||
// BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>>
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub signatures: Option<Box<RawJsonValue>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub rejected: bool,
|
||||
}
|
||||
|
||||
/// Content hashes of a PDU.
|
||||
@@ -254,3 +257,35 @@ impl Ord for Pdu {
|
||||
impl PartialOrd for Pdu {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
|
||||
}
|
||||
|
||||
impl ruma::state_res::Event for Pdu {
|
||||
type Id = OwnedEventId;
|
||||
|
||||
fn event_id(&self) -> &Self::Id { &self.event_id }
|
||||
|
||||
fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() }
|
||||
|
||||
fn sender(&self) -> &UserId { self.sender.as_ref() }
|
||||
|
||||
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
|
||||
MilliSecondsSinceUnixEpoch(self.origin_server_ts)
|
||||
}
|
||||
|
||||
fn event_type(&self) -> &TimelineEventType { &self.kind }
|
||||
|
||||
fn content(&self) -> &RawJsonValue { &self.content }
|
||||
|
||||
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
|
||||
|
||||
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
Box::new(self.prev_events.iter())
|
||||
}
|
||||
|
||||
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
Box::new(self.auth_events.iter())
|
||||
}
|
||||
|
||||
fn redacts(&self) -> Option<&Self::Id> { self.redacts.as_ref() }
|
||||
|
||||
fn rejected(&self) -> bool { self.rejected }
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//! 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.
|
||||
@@ -1,554 +0,0 @@
|
||||
#[cfg(conduwuit_bench)]
|
||||
extern crate test;
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::atomic::{AtomicU64, Ordering::SeqCst},
|
||||
};
|
||||
|
||||
use futures::{future, future::ready};
|
||||
use maplit::{btreemap, hashmap, hashset};
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, Signatures, UserId,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
int, room_id, uint, user_id,
|
||||
};
|
||||
use serde_json::{
|
||||
json,
|
||||
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
matrix::{Event, Pdu, pdu::EventHash},
|
||||
state_res::{self as state_res, Error, Result, StateMap},
|
||||
};
|
||||
|
||||
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[cfg(conduwuit_bench)]
|
||||
#[cfg_attr(conduwuit_bench, bench)]
|
||||
fn lexico_topo_sort(c: &mut test::Bencher) {
|
||||
let graph = hashmap! {
|
||||
event_id("l") => hashset![event_id("o")],
|
||||
event_id("m") => hashset![event_id("n"), event_id("o")],
|
||||
event_id("n") => hashset![event_id("o")],
|
||||
event_id("o") => hashset![], // "o" has zero outgoing edges but 4 incoming edges
|
||||
event_id("p") => hashset![event_id("o")],
|
||||
};
|
||||
|
||||
c.iter(|| {
|
||||
let _ = state_res::lexicographical_topological_sort(&graph, &|_| {
|
||||
future::ok((int!(0), MilliSecondsSinceUnixEpoch(uint!(0))))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(conduwuit_bench)]
|
||||
#[cfg_attr(conduwuit_bench, bench)]
|
||||
fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
|
||||
let mut store = TestStore(hashmap! {});
|
||||
|
||||
// build up the DAG
|
||||
let (state_at_bob, state_at_charlie, _) = store.set_up();
|
||||
|
||||
c.iter(|| async {
|
||||
let ev_map = store.0.clone();
|
||||
let state_sets = [&state_at_bob, &state_at_charlie];
|
||||
let fetch = |id: OwnedEventId| ready(ev_map.get(&id).map(ToOwned::to_owned));
|
||||
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
|
||||
let auth_chain_sets: Vec<HashSet<_>> = state_sets
|
||||
.iter()
|
||||
.map(|map| {
|
||||
store
|
||||
.auth_event_ids(room_id(), map.values().cloned().collect())
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let _ = match state_res::resolve(
|
||||
&RoomVersionId::V6,
|
||||
state_sets.into_iter(),
|
||||
&auth_chain_sets,
|
||||
&fetch,
|
||||
&exists,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(state) => state,
|
||||
| Err(e) => panic!("{e}"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(conduwuit_bench)]
|
||||
#[cfg_attr(conduwuit_bench, bench)]
|
||||
fn resolve_deeper_event_set(c: &mut test::Bencher) {
|
||||
let mut inner = INITIAL_EVENTS();
|
||||
let ban = BAN_STATE_SET();
|
||||
|
||||
inner.extend(ban);
|
||||
let store = TestStore(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.event_type().clone().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
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.event_type().clone().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
c.iter(|| async {
|
||||
let state_sets = [&state_set_a, &state_set_b];
|
||||
let auth_chain_sets: Vec<HashSet<_>> = state_sets
|
||||
.iter()
|
||||
.map(|map| {
|
||||
store
|
||||
.auth_event_ids(room_id(), map.values().cloned().collect())
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fetch = |id: OwnedEventId| ready(inner.get(&id).map(ToOwned::to_owned));
|
||||
let exists = |id: OwnedEventId| ready(inner.get(&id).is_some());
|
||||
let _ = match state_res::resolve(
|
||||
&RoomVersionId::V6,
|
||||
state_sets.into_iter(),
|
||||
&auth_chain_sets,
|
||||
&fetch,
|
||||
&exists,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(state) => state,
|
||||
| Err(_) => panic!("resolution failed during benchmarking"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//*/////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION DETAILS AHEAD
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////*/
|
||||
struct TestStore<E: Event>(HashMap<OwnedEventId, E>);
|
||||
|
||||
#[allow(unused)]
|
||||
impl<E: Event + Clone> TestStore<E> {
|
||||
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
|
||||
self.0
|
||||
.get(event_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::NotFound(format!("{} not found", event_id)))
|
||||
}
|
||||
|
||||
/// Returns the events that correspond to the `event_ids` sorted in the same
|
||||
/// order.
|
||||
fn get_events(&self, room_id: &RoomId, event_ids: &[OwnedEventId]) -> Result<Vec<E>> {
|
||||
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: Vec<OwnedEventId>,
|
||||
) -> Result<HashSet<OwnedEventId>> {
|
||||
let mut result = HashSet::new();
|
||||
let mut stack = event_ids;
|
||||
|
||||
// DFS for auth event chain
|
||||
while !stack.is_empty() {
|
||||
let ev_id = stack.pop().unwrap();
|
||||
if result.contains(&ev_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.insert(ev_id.clone());
|
||||
|
||||
let event = self.get_event(room_id, ev_id.borrow())?;
|
||||
|
||||
stack.extend(event.auth_events().map(ToOwned::to_owned));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Returns a vector representing the difference in auth chains of the given
|
||||
/// `events`.
|
||||
fn auth_chain_diff(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_ids: Vec<Vec<OwnedEventId>>,
|
||||
) -> Result<Vec<OwnedEventId>> {
|
||||
let mut auth_chain_sets = 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::<HashSet<_>>();
|
||||
auth_chain_sets.push(chain);
|
||||
}
|
||||
|
||||
if let Some(first) = auth_chain_sets.first().cloned() {
|
||||
let common = auth_chain_sets
|
||||
.iter()
|
||||
.skip(1)
|
||||
.fold(first, |a, b| a.intersection(b).cloned().collect::<HashSet<_>>());
|
||||
|
||||
Ok(auth_chain_sets
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|id| !common.contains(id))
|
||||
.collect())
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestStore<Pdu> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn set_up(
|
||||
&mut self,
|
||||
) -> (StateMap<OwnedEventId>, StateMap<OwnedEventId>, StateMap<OwnedEventId>) {
|
||||
let create_event = to_pdu_event::<&EventId>(
|
||||
"CREATE",
|
||||
alice(),
|
||||
TimelineEventType::RoomCreate,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
let cre = create_event.event_id().to_owned();
|
||||
self.0.insert(cre.clone(), create_event.clone());
|
||||
|
||||
let alice_mem = to_pdu_event(
|
||||
"IMA",
|
||||
alice(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(alice().to_string().as_str()),
|
||||
member_content_join(),
|
||||
&[cre.clone()],
|
||||
&[cre.clone()],
|
||||
);
|
||||
self.0
|
||||
.insert(alice_mem.event_id().to_owned(), alice_mem.clone());
|
||||
|
||||
let join_rules = to_pdu_event(
|
||||
"IJR",
|
||||
alice(),
|
||||
TimelineEventType::RoomJoinRules,
|
||||
Some(""),
|
||||
to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public)).unwrap(),
|
||||
&[cre.clone(), alice_mem.event_id().to_owned()],
|
||||
&[alice_mem.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(join_rules.event_id().to_owned(), 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(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(bob().to_string().as_str()),
|
||||
member_content_join(),
|
||||
&[cre.clone(), join_rules.event_id().to_owned()],
|
||||
&[join_rules.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(bob_mem.event_id().to_owned(), bob_mem.clone());
|
||||
|
||||
let charlie_mem = to_pdu_event(
|
||||
"IMC",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(charlie().to_string().as_str()),
|
||||
member_content_join(),
|
||||
&[cre, join_rules.event_id().to_owned()],
|
||||
&[join_rules.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(charlie_mem.event_id().to_owned(), charlie_mem.clone());
|
||||
|
||||
let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem]
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem]
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
let expected = [&create_event, &alice_mem, &join_rules, &bob_mem, &charlie_mem]
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
(state_at_bob, state_at_charlie, expected)
|
||||
}
|
||||
}
|
||||
|
||||
fn event_id(id: &str) -> OwnedEventId {
|
||||
if id.contains('$') {
|
||||
return id.try_into().unwrap();
|
||||
}
|
||||
format!("${}:foo", id).try_into().unwrap()
|
||||
}
|
||||
|
||||
fn alice() -> &'static UserId { user_id!("@alice:foo") }
|
||||
|
||||
fn bob() -> &'static UserId { user_id!("@bob:foo") }
|
||||
|
||||
fn charlie() -> &'static UserId { user_id!("@charlie:foo") }
|
||||
|
||||
fn ella() -> &'static UserId { user_id!("@ella:foo") }
|
||||
|
||||
fn room_id() -> &'static RoomId { room_id!("!test:foo") }
|
||||
|
||||
fn member_content_ban() -> Box<RawJsonValue> {
|
||||
to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Ban)).unwrap()
|
||||
}
|
||||
|
||||
fn member_content_join() -> Box<RawJsonValue> {
|
||||
to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Join)).unwrap()
|
||||
}
|
||||
|
||||
fn to_pdu_event<S>(
|
||||
id: &str,
|
||||
sender: &UserId,
|
||||
ev_type: TimelineEventType,
|
||||
state_key: Option<&str>,
|
||||
content: Box<RawJsonValue>,
|
||||
auth_events: &[S],
|
||||
prev_events: &[S],
|
||||
) -> Pdu
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
// We don't care if the addition happens in order just that it is atomic
|
||||
// (each event has its own value)
|
||||
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
|
||||
let id = if id.contains('$') {
|
||||
id.to_owned()
|
||||
} else {
|
||||
format!("${}:foo", id)
|
||||
};
|
||||
let auth_events = auth_events
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(event_id)
|
||||
.collect::<Vec<_>>();
|
||||
let prev_events = prev_events
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(event_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Pdu {
|
||||
event_id: id.try_into().unwrap(),
|
||||
room_id: Some(room_id().to_owned()),
|
||||
sender: sender.to_owned(),
|
||||
origin_server_ts: ts.try_into().unwrap(),
|
||||
state_key: state_key.map(Into::into),
|
||||
kind: ev_type,
|
||||
content,
|
||||
origin: None,
|
||||
redacts: None,
|
||||
sticky: None,
|
||||
unsigned: None,
|
||||
auth_events,
|
||||
prev_events,
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: String::new() },
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
// all graphs start with these input events
|
||||
#[allow(non_snake_case)]
|
||||
fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Pdu> {
|
||||
vec![
|
||||
to_pdu_event::<&EventId>(
|
||||
"CREATE",
|
||||
alice(),
|
||||
TimelineEventType::RoomCreate,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMA",
|
||||
alice(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(alice().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE"],
|
||||
&["CREATE"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IPOWER",
|
||||
alice(),
|
||||
TimelineEventType::RoomPowerLevels,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "users": { alice(): 100 } })).unwrap(),
|
||||
&["CREATE", "IMA"],
|
||||
&["IMA"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IJR",
|
||||
alice(),
|
||||
TimelineEventType::RoomJoinRules,
|
||||
Some(""),
|
||||
to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public)).unwrap(),
|
||||
&["CREATE", "IMA", "IPOWER"],
|
||||
&["IPOWER"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMB",
|
||||
bob(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(bob().to_string().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE", "IJR", "IPOWER"],
|
||||
&["IJR"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMC",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(charlie().to_string().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE", "IJR", "IPOWER"],
|
||||
&["IMB"],
|
||||
),
|
||||
to_pdu_event::<&EventId>(
|
||||
"START",
|
||||
charlie(),
|
||||
TimelineEventType::RoomTopic,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({})).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
to_pdu_event::<&EventId>(
|
||||
"END",
|
||||
charlie(),
|
||||
TimelineEventType::RoomTopic,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({})).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|ev| (ev.event_id().to_owned(), ev))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// all graphs start with these input events
|
||||
#[allow(non_snake_case)]
|
||||
fn BAN_STATE_SET() -> HashMap<OwnedEventId, Pdu> {
|
||||
vec![
|
||||
to_pdu_event(
|
||||
"PA",
|
||||
alice(),
|
||||
TimelineEventType::RoomPowerLevels,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "users": { alice(): 100, bob(): 50 } })).unwrap(),
|
||||
&["CREATE", "IMA", "IPOWER"], // auth_events
|
||||
&["START"], // prev_events
|
||||
),
|
||||
to_pdu_event(
|
||||
"PB",
|
||||
alice(),
|
||||
TimelineEventType::RoomPowerLevels,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "users": { alice(): 100, bob(): 50 } })).unwrap(),
|
||||
&["CREATE", "IMA", "IPOWER"],
|
||||
&["END"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"MB",
|
||||
alice(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(ella().as_str()),
|
||||
member_content_ban(),
|
||||
&["CREATE", "IMA", "PB"],
|
||||
&["PA"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IME",
|
||||
ella(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(ella().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE", "IJR", "PA"],
|
||||
&["MB"],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|ev| (ev.event_id().to_owned(), ev))
|
||||
.collect()
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use serde_json::Error as JsonError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Represents the various errors that arise when resolving state.
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// A deserialization error.
|
||||
#[error(transparent)]
|
||||
SerdeJson(#[from] JsonError),
|
||||
|
||||
/// 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),
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,104 +0,0 @@
|
||||
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]
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
6/30/2021 BRANCH: state-closure REV: 174c3e2a72232ad75b3fb14b3551f5f746f4fe84
|
||||
lexicographical topological sort
|
||||
time: [1.5496 us 1.5536 us 1.5586 us]
|
||||
Found 9 outliers among 100 measurements (9.00%)
|
||||
1 (1.00%) low mild
|
||||
1 (1.00%) high mild
|
||||
7 (7.00%) high severe
|
||||
|
||||
resolve state of 5 events one fork
|
||||
time: [10.319 us 10.333 us 10.347 us]
|
||||
Found 2 outliers among 100 measurements (2.00%)
|
||||
2 (2.00%) high severe
|
||||
|
||||
resolve state of 10 events 3 conflicting
|
||||
time: [25.770 us 25.805 us 25.839 us]
|
||||
Found 7 outliers among 100 measurements (7.00%)
|
||||
5 (5.00%) high mild
|
||||
2 (2.00%) high severe
|
||||
|
||||
7/20/2021 BRANCH stateres-result REV:
|
||||
This marks the switch to HashSet/Map
|
||||
lexicographical topological sort
|
||||
time: [1.8122 us 1.8177 us 1.8233 us]
|
||||
change: [+15.205% +15.919% +16.502%] (p = 0.00 < 0.05)
|
||||
Performance has regressed.
|
||||
Found 7 outliers among 100 measurements (7.00%)
|
||||
5 (5.00%) high mild
|
||||
2 (2.00%) high severe
|
||||
|
||||
resolve state of 5 events one fork
|
||||
time: [11.966 us 12.010 us 12.059 us]
|
||||
change: [+16.089% +16.730% +17.469%] (p = 0.00 < 0.05)
|
||||
Performance has regressed.
|
||||
Found 7 outliers among 100 measurements (7.00%)
|
||||
3 (3.00%) high mild
|
||||
4 (4.00%) high severe
|
||||
|
||||
resolve state of 10 events 3 conflicting
|
||||
time: [29.092 us 29.201 us 29.311 us]
|
||||
change: [+12.447% +12.847% +13.280%] (p = 0.00 < 0.05)
|
||||
Performance has regressed.
|
||||
Found 9 outliers among 100 measurements (9.00%)
|
||||
6 (6.00%) high mild
|
||||
3 (3.00%) high severe
|
||||
@@ -1,241 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
Int, OwnedUserId, UserId,
|
||||
events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent},
|
||||
power_levels::{NotificationPowerLevels, default_power_level},
|
||||
room_version_rules::{AuthorizationRules, RoomVersionRules},
|
||||
serde::deserialize_v1_powerlevel,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Error, from_str as from_json_str};
|
||||
|
||||
use super::{
|
||||
Result,
|
||||
serde_backports::{
|
||||
vec_deserialize_int_powerlevel_values, vec_deserialize_v1_powerlevel_values,
|
||||
},
|
||||
};
|
||||
use crate::error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IntRoomPowerLevelsEventContent {
|
||||
#[serde(default = "default_power_level")]
|
||||
ban: Int,
|
||||
|
||||
#[serde(default)]
|
||||
events: BTreeMap<TimelineEventType, Int>,
|
||||
|
||||
#[serde(default)]
|
||||
events_default: Int,
|
||||
|
||||
#[serde(default)]
|
||||
invite: Int,
|
||||
|
||||
#[serde(default = "default_power_level")]
|
||||
kick: Int,
|
||||
|
||||
#[serde(default = "default_power_level")]
|
||||
redact: Int,
|
||||
|
||||
#[serde(default = "default_power_level")]
|
||||
state_default: Int,
|
||||
|
||||
#[serde(default)]
|
||||
users: BTreeMap<OwnedUserId, Int>,
|
||||
|
||||
#[serde(default)]
|
||||
users_default: Int,
|
||||
|
||||
#[serde(default)]
|
||||
notifications: IntNotificationPowerLevels,
|
||||
}
|
||||
|
||||
impl IntRoomPowerLevelsEventContent {
|
||||
fn into_room_power_levels_content(
|
||||
self,
|
||||
auth_rules: &AuthorizationRules,
|
||||
) -> RoomPowerLevelsEventContent {
|
||||
let Self {
|
||||
ban,
|
||||
events,
|
||||
events_default,
|
||||
invite,
|
||||
kick,
|
||||
redact,
|
||||
state_default,
|
||||
users,
|
||||
users_default,
|
||||
notifications,
|
||||
} = self;
|
||||
|
||||
let mut pl = RoomPowerLevelsEventContent::new(auth_rules);
|
||||
pl.ban = ban;
|
||||
pl.events = events;
|
||||
pl.events_default = events_default;
|
||||
pl.invite = invite;
|
||||
pl.kick = kick;
|
||||
pl.redact = redact;
|
||||
pl.state_default = state_default;
|
||||
pl.users = users;
|
||||
pl.users_default = users_default;
|
||||
pl.notifications = notifications.into();
|
||||
|
||||
pl
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IntNotificationPowerLevels {
|
||||
#[serde(default = "default_power_level")]
|
||||
room: Int,
|
||||
}
|
||||
|
||||
impl Default for IntNotificationPowerLevels {
|
||||
fn default() -> Self { Self { room: default_power_level() } }
|
||||
}
|
||||
|
||||
impl From<IntNotificationPowerLevels> for NotificationPowerLevels {
|
||||
fn from(int_notif: IntNotificationPowerLevels) -> Self {
|
||||
let mut notif = Self::new();
|
||||
notif.room = int_notif.room;
|
||||
|
||||
notif
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_power_levels(
|
||||
content: &str,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Option<RoomPowerLevelsEventContent> {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
deserialize_integer_power_levels(content, &room_version.authorization)
|
||||
} else {
|
||||
deserialize_legacy_power_levels(content)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_integer_power_levels(
|
||||
content: &str,
|
||||
auth_rules: &AuthorizationRules,
|
||||
) -> Option<RoomPowerLevelsEventContent> {
|
||||
match from_json_str::<IntRoomPowerLevelsEventContent>(content) {
|
||||
| Ok(content) => Some(content.into_room_power_levels_content(auth_rules)),
|
||||
| Err(_) => {
|
||||
error!("m.room.power_levels event is not valid with integer values");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_legacy_power_levels(content: &str) -> Option<RoomPowerLevelsEventContent> {
|
||||
match from_json_str(content) {
|
||||
| Ok(content) => Some(content),
|
||||
| Err(_) => {
|
||||
error!(
|
||||
"m.room.power_levels event is not valid with integer or string integer values"
|
||||
);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct PowerLevelsContentFields {
|
||||
#[serde(default, deserialize_with = "vec_deserialize_v1_powerlevel_values")]
|
||||
pub(crate) users: Vec<(OwnedUserId, Int)>,
|
||||
|
||||
#[serde(default, deserialize_with = "deserialize_v1_powerlevel")]
|
||||
pub(crate) users_default: Int,
|
||||
}
|
||||
|
||||
impl PowerLevelsContentFields {
|
||||
pub(crate) fn get_user_power(&self, user_id: &UserId) -> Option<&Int> {
|
||||
let comparator = |item: &(OwnedUserId, Int)| {
|
||||
let item: &UserId = &item.0;
|
||||
item.cmp(user_id)
|
||||
};
|
||||
|
||||
self.users
|
||||
.binary_search_by(comparator)
|
||||
.ok()
|
||||
.and_then(|idx| self.users.get(idx).map(|item| &item.1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IntPowerLevelsContentFields {
|
||||
#[serde(default, deserialize_with = "vec_deserialize_int_powerlevel_values")]
|
||||
users: Vec<(OwnedUserId, Int)>,
|
||||
|
||||
#[serde(default)]
|
||||
users_default: Int,
|
||||
}
|
||||
|
||||
impl From<IntPowerLevelsContentFields> for PowerLevelsContentFields {
|
||||
fn from(pl: IntPowerLevelsContentFields) -> Self {
|
||||
let IntPowerLevelsContentFields { users, users_default } = pl;
|
||||
Self { users, users_default }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct PowerLevelsContentInvite {
|
||||
#[serde(default, deserialize_with = "deserialize_v1_powerlevel")]
|
||||
pub(crate) invite: Int,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IntPowerLevelsContentInvite {
|
||||
#[serde(default)]
|
||||
invite: Int,
|
||||
}
|
||||
|
||||
impl From<IntPowerLevelsContentInvite> for PowerLevelsContentInvite {
|
||||
fn from(pl: IntPowerLevelsContentInvite) -> Self {
|
||||
let IntPowerLevelsContentInvite { invite } = pl;
|
||||
Self { invite }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_power_levels_content_invite(
|
||||
content: &str,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Result<PowerLevelsContentInvite, Error> {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
from_json_str::<IntPowerLevelsContentInvite>(content).map(Into::into)
|
||||
} else {
|
||||
from_json_str(content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct PowerLevelsContentRedact {
|
||||
#[serde(default = "default_power_level", deserialize_with = "deserialize_v1_powerlevel")]
|
||||
pub(crate) redact: Int,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct IntPowerLevelsContentRedact {
|
||||
#[serde(default = "default_power_level")]
|
||||
redact: Int,
|
||||
}
|
||||
|
||||
impl From<IntPowerLevelsContentRedact> for PowerLevelsContentRedact {
|
||||
fn from(pl: IntPowerLevelsContentRedact) -> Self {
|
||||
let IntPowerLevelsContentRedact { redact } = pl;
|
||||
Self { redact }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_power_levels_content_redact(
|
||||
content: &str,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Result<PowerLevelsContentRedact, Error> {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
from_json_str::<IntPowerLevelsContentRedact>(content).map(Into::into)
|
||||
} else {
|
||||
from_json_str(content)
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
//! These functions are copied from an old version of Ruma. power_levels.rs uses
|
||||
//! them to lazily deserialize power level events. Upstream Ruma uses a much
|
||||
//! more elegant approach in its state resolution code, which we may want
|
||||
//! to look into at some point.
|
||||
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
use ruma::{Int, serde::deserialize_v1_powerlevel};
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
de::{MapAccess, Visitor},
|
||||
};
|
||||
|
||||
/// Take a Map with values of either an integer number or a string and
|
||||
/// deserialize those to integer numbers in a Vec of sorted pairs.
|
||||
///
|
||||
/// To be used like this:
|
||||
/// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]`
|
||||
pub(super) fn vec_deserialize_v1_powerlevel_values<'de, D, T>(
|
||||
de: D,
|
||||
) -> Result<Vec<(T, Int)>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
#[repr(transparent)]
|
||||
struct IntWrap(Int);
|
||||
|
||||
impl<'de> Deserialize<'de> for IntWrap {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserialize_v1_powerlevel(deserializer).map(IntWrap)
|
||||
}
|
||||
}
|
||||
|
||||
struct IntMapVisitor<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IntMapVisitor<T> {
|
||||
fn new() -> Self { Self { _phantom: PhantomData } }
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
type Value = Vec<(T, Int)>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map with integers or strings as values")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut res = Vec::new();
|
||||
if let Some(hint) = map.size_hint() {
|
||||
res.reserve(hint);
|
||||
}
|
||||
|
||||
while let Some((k, IntWrap(v))) = map.next_entry()? {
|
||||
res.push((k, v));
|
||||
}
|
||||
|
||||
res.sort_unstable();
|
||||
res.dedup_by(|a, b| a.0 == b.0);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(IntMapVisitor::new())
|
||||
}
|
||||
|
||||
/// Take a Map with integer values and deserialize those to a Vec of sorted
|
||||
/// pairs
|
||||
///
|
||||
/// To be used like this:
|
||||
/// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]`
|
||||
pub(super) fn vec_deserialize_int_powerlevel_values<'de, D, T>(
|
||||
de: D,
|
||||
) -> Result<Vec<(T, Int)>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
struct IntMapVisitor<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IntMapVisitor<T> {
|
||||
fn new() -> Self { Self { _phantom: PhantomData } }
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
type Value = Vec<(T, Int)>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map with integers as values")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut res = Vec::new();
|
||||
if let Some(hint) = map.size_hint() {
|
||||
res.reserve(hint);
|
||||
}
|
||||
|
||||
while let Some(item) = map.next_entry()? {
|
||||
res.push(item);
|
||||
}
|
||||
|
||||
res.sort_unstable();
|
||||
res.dedup_by(|a, b| a.0 == b.0);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(IntMapVisitor::new())
|
||||
}
|
||||
@@ -1,582 +0,0 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
sync::atomic::{AtomicU64, Ordering::SeqCst},
|
||||
};
|
||||
|
||||
use futures::future::ready;
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures,
|
||||
UserId, event_id,
|
||||
events::{
|
||||
TimelineEventType,
|
||||
room::{
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
int, room_id,
|
||||
room_version_rules::RoomVersionRules,
|
||||
uint, user_id,
|
||||
};
|
||||
use serde_json::{
|
||||
json,
|
||||
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
|
||||
};
|
||||
|
||||
use super::auth_types_for_event;
|
||||
use crate::{
|
||||
Result, info,
|
||||
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
|
||||
};
|
||||
|
||||
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub(crate) async fn do_check(
|
||||
events: &[Pdu],
|
||||
edges: Vec<Vec<OwnedEventId>>,
|
||||
expected_state_ids: Vec<OwnedEventId>,
|
||||
) {
|
||||
// To activate logging use `RUST_LOG=debug cargo t`
|
||||
|
||||
let init_events = INITIAL_EVENTS();
|
||||
|
||||
let mut store = TestStore(
|
||||
init_events
|
||||
.values()
|
||||
.chain(events)
|
||||
.map(|ev| (ev.event_id().to_owned(), ev.clone()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// This will be lexi_topo_sorted for resolution
|
||||
let mut graph = HashMap::new();
|
||||
// This is the same as in `resolve` event_id -> OriginalStateEvent
|
||||
let mut fake_event_map = HashMap::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 init_events.values().chain(events) {
|
||||
graph.insert(ev.event_id().to_owned(), HashSet::new());
|
||||
fake_event_map.insert(ev.event_id().to_owned(), ev.clone());
|
||||
}
|
||||
|
||||
for pair in INITIAL_EDGES().windows(2) {
|
||||
if let [a, b] = &pair {
|
||||
graph
|
||||
.entry(a.to_owned())
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(b.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for edge_list in edges {
|
||||
for pair in edge_list.windows(2) {
|
||||
if let [a, b] = &pair {
|
||||
graph
|
||||
.entry(a.to_owned())
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(b.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// event_id -> Pdu
|
||||
let mut event_map: HashMap<OwnedEventId, Pdu> = HashMap::new();
|
||||
// event_id -> StateMap<OwnedEventId>
|
||||
let mut state_at_event: HashMap<OwnedEventId, StateMap<OwnedEventId>> = HashMap::new();
|
||||
|
||||
// Resolve the current state and add it to the state_at_event map then
|
||||
// continue on in "time"
|
||||
for node in super::lexicographical_topological_sort(&graph, &|_id| async {
|
||||
Ok((int!(0), MilliSecondsSinceUnixEpoch(uint!(0))))
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
let fake_event = fake_event_map.get(&node).unwrap();
|
||||
let event_id = fake_event.event_id().to_owned();
|
||||
|
||||
let prev_events = graph.get(&node).unwrap();
|
||||
|
||||
let state_before: StateMap<OwnedEventId> = if prev_events.is_empty() {
|
||||
HashMap::new()
|
||||
} else if prev_events.len() == 1 {
|
||||
state_at_event
|
||||
.get(prev_events.iter().next().unwrap())
|
||||
.unwrap()
|
||||
.clone()
|
||||
} else {
|
||||
let state_sets = prev_events
|
||||
.iter()
|
||||
.filter_map(|k| state_at_event.get(k))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
info!(
|
||||
"{:#?}",
|
||||
state_sets
|
||||
.iter()
|
||||
.map(|map| map
|
||||
.iter()
|
||||
.map(|((ty, key), id)| format!("(({ty}{key:?}), {id})"))
|
||||
.collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let auth_chain_sets: Vec<_> = state_sets
|
||||
.iter()
|
||||
.map(|map| {
|
||||
store
|
||||
.auth_event_ids(room_id(), map.values().cloned().collect())
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let event_map = &event_map;
|
||||
let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned());
|
||||
let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some());
|
||||
let resolved = super::resolve(
|
||||
&RoomVersionRules::V6,
|
||||
state_sets,
|
||||
&auth_chain_sets,
|
||||
&fetch,
|
||||
&exists,
|
||||
)
|
||||
.await;
|
||||
|
||||
match resolved {
|
||||
| Ok(state) => state,
|
||||
| Err(e) => panic!("resolution for {node} failed: {e}"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut state_after = state_before.clone();
|
||||
|
||||
let ty = fake_event.event_type();
|
||||
let key = fake_event.state_key().unwrap();
|
||||
state_after.insert(ty.with_state_key(key), event_id.to_owned());
|
||||
|
||||
let auth_types = auth_types_for_event(
|
||||
fake_event.event_type(),
|
||||
fake_event.sender(),
|
||||
fake_event.state_key(),
|
||||
fake_event.content(),
|
||||
&RoomVersionRules::V6,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
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().as_str(),
|
||||
e.sender(),
|
||||
e.event_type().clone(),
|
||||
e.state_key(),
|
||||
e.content().to_owned(),
|
||||
&auth_events,
|
||||
&prev_events.iter().cloned().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// 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.to_owned(), event.clone());
|
||||
|
||||
state_at_event.insert(node, state_after);
|
||||
event_map.insert(event_id.to_owned(), store.0.get(ev_id).unwrap().clone());
|
||||
}
|
||||
|
||||
let mut expected_state = StateMap::new();
|
||||
for node in expected_state_ids {
|
||||
let ev = event_map.get(&node).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"{node} not found in {:?}",
|
||||
event_map
|
||||
.keys()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
});
|
||||
|
||||
let key = ev.event_type().with_state_key(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)
|
||||
// Filter out the dummy messages events.
|
||||
// These act as points in time where there should be a known state to
|
||||
// test against.
|
||||
&& **k != ("m.room.message".into(), "dummy".into())
|
||||
})
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect::<StateMap<OwnedEventId>>();
|
||||
|
||||
assert_eq!(expected_state, end_state);
|
||||
}
|
||||
|
||||
#[allow(clippy::exhaustive_structs)]
|
||||
pub(crate) struct TestStore<E: Event>(pub(crate) HashMap<OwnedEventId, E>);
|
||||
|
||||
impl<E: Event + Clone> TestStore<E> {
|
||||
pub(crate) fn get_event(&self, _: &RoomId, event_id: &EventId) -> Result<E> {
|
||||
self.0
|
||||
.get(event_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| super::Error::NotFound(format!("{event_id} not found")))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns a Vec of the related auth events to the given `event`.
|
||||
pub(crate) fn auth_event_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_ids: Vec<OwnedEventId>,
|
||||
) -> Result<HashSet<OwnedEventId>> {
|
||||
let mut result = HashSet::new();
|
||||
let mut stack = event_ids;
|
||||
|
||||
// DFS for auth event chain
|
||||
while let Some(ev_id) = stack.pop() {
|
||||
if result.contains(&ev_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.insert(ev_id.clone());
|
||||
|
||||
let event = self.get_event(room_id, ev_id.borrow())?;
|
||||
|
||||
stack.extend(event.auth_events().map(ToOwned::to_owned));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
// A StateStore implementation for testing
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl TestStore<Pdu> {
|
||||
pub(crate) fn set_up(
|
||||
&mut self,
|
||||
) -> (StateMap<OwnedEventId>, StateMap<OwnedEventId>, StateMap<OwnedEventId>) {
|
||||
let create_event = to_pdu_event::<&EventId>(
|
||||
"CREATE",
|
||||
alice(),
|
||||
TimelineEventType::RoomCreate,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
let cre = create_event.event_id().to_owned();
|
||||
self.0.insert(cre.clone(), create_event.clone());
|
||||
|
||||
let alice_mem = to_pdu_event(
|
||||
"IMA",
|
||||
alice(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(alice().as_str()),
|
||||
member_content_join(),
|
||||
&[cre.clone()],
|
||||
&[cre.clone()],
|
||||
);
|
||||
self.0
|
||||
.insert(alice_mem.event_id().to_owned(), alice_mem.clone());
|
||||
|
||||
let join_rules = to_pdu_event(
|
||||
"IJR",
|
||||
alice(),
|
||||
TimelineEventType::RoomJoinRules,
|
||||
Some(""),
|
||||
to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public)).unwrap(),
|
||||
&[cre.clone(), alice_mem.event_id().to_owned()],
|
||||
&[alice_mem.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(join_rules.event_id().to_owned(), 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(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(bob().as_str()),
|
||||
member_content_join(),
|
||||
&[cre.clone(), join_rules.event_id().to_owned()],
|
||||
&[join_rules.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(bob_mem.event_id().to_owned(), bob_mem.clone());
|
||||
|
||||
let charlie_mem = to_pdu_event(
|
||||
"IMC",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(charlie().as_str()),
|
||||
member_content_join(),
|
||||
&[cre, join_rules.event_id().to_owned()],
|
||||
&[join_rules.event_id().to_owned()],
|
||||
);
|
||||
self.0
|
||||
.insert(charlie_mem.event_id().to_owned(), charlie_mem.clone());
|
||||
|
||||
let state_at_bob = [&create_event, &alice_mem, &join_rules, &bob_mem]
|
||||
.iter()
|
||||
.map(|e| {
|
||||
(e.event_type().with_state_key(e.state_key().unwrap()), e.event_id().to_owned())
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
let state_at_charlie = [&create_event, &alice_mem, &join_rules, &charlie_mem]
|
||||
.iter()
|
||||
.map(|e| {
|
||||
(e.event_type().with_state_key(e.state_key().unwrap()), e.event_id().to_owned())
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
let expected = [&create_event, &alice_mem, &join_rules, &bob_mem, &charlie_mem]
|
||||
.iter()
|
||||
.map(|e| {
|
||||
(e.event_type().with_state_key(e.state_key().unwrap()), e.event_id().to_owned())
|
||||
})
|
||||
.collect::<StateMap<_>>();
|
||||
|
||||
(state_at_bob, state_at_charlie, expected)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn event_id(id: &str) -> OwnedEventId {
|
||||
if id.contains('$') {
|
||||
return id.try_into().unwrap();
|
||||
}
|
||||
|
||||
format!("${id}:foo").try_into().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn alice() -> &'static UserId { user_id!("@alice:foo") }
|
||||
|
||||
pub(crate) fn bob() -> &'static UserId { user_id!("@bob:foo") }
|
||||
|
||||
pub(crate) fn charlie() -> &'static UserId { user_id!("@charlie:foo") }
|
||||
|
||||
pub(crate) fn ella() -> &'static UserId { user_id!("@ella:foo") }
|
||||
|
||||
pub(crate) fn zara() -> &'static UserId { user_id!("@zara:foo") }
|
||||
|
||||
pub(crate) fn room_id() -> &'static RoomId { room_id!("!test:foo") }
|
||||
|
||||
pub(crate) fn member_content_ban() -> Box<RawJsonValue> {
|
||||
to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Ban)).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn member_content_join() -> Box<RawJsonValue> {
|
||||
to_raw_json_value(&RoomMemberEventContent::new(MembershipState::Join)).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn to_init_pdu_event(
|
||||
id: &str,
|
||||
sender: &UserId,
|
||||
ev_type: TimelineEventType,
|
||||
state_key: Option<&str>,
|
||||
content: Box<RawJsonValue>,
|
||||
) -> Pdu {
|
||||
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
|
||||
let id = if id.contains('$') {
|
||||
id.to_owned()
|
||||
} else {
|
||||
format!("${id}:foo")
|
||||
};
|
||||
|
||||
Pdu {
|
||||
event_id: id.try_into().unwrap(),
|
||||
room_id: Some(room_id().to_owned()),
|
||||
sender: sender.to_owned(),
|
||||
origin_server_ts: ts.try_into().unwrap(),
|
||||
state_key: state_key.map(Into::into),
|
||||
kind: ev_type,
|
||||
content,
|
||||
origin: None,
|
||||
redacts: None,
|
||||
sticky: None,
|
||||
unsigned: None,
|
||||
auth_events: vec![],
|
||||
prev_events: vec![],
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: "".to_owned() },
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdu_event<S>(
|
||||
id: &str,
|
||||
sender: &UserId,
|
||||
ev_type: TimelineEventType,
|
||||
state_key: Option<&str>,
|
||||
content: Box<RawJsonValue>,
|
||||
auth_events: &[S],
|
||||
prev_events: &[S],
|
||||
) -> Pdu
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
|
||||
let id = if id.contains('$') {
|
||||
id.to_owned()
|
||||
} else {
|
||||
format!("${id}:foo")
|
||||
};
|
||||
let auth_events = auth_events
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(event_id)
|
||||
.collect::<Vec<_>>();
|
||||
let prev_events = prev_events
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(event_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Pdu {
|
||||
event_id: id.try_into().unwrap(),
|
||||
room_id: Some(room_id().to_owned()),
|
||||
sender: sender.to_owned(),
|
||||
origin_server_ts: ts.try_into().unwrap(),
|
||||
state_key: state_key.map(Into::into),
|
||||
kind: ev_type,
|
||||
content,
|
||||
origin: None,
|
||||
redacts: None,
|
||||
sticky: None,
|
||||
unsigned: None,
|
||||
auth_events,
|
||||
prev_events,
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: "".to_owned() },
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
// all graphs start with these input events
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Pdu> {
|
||||
vec![
|
||||
to_pdu_event::<&EventId>(
|
||||
"CREATE",
|
||||
alice(),
|
||||
TimelineEventType::RoomCreate,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMA",
|
||||
alice(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(alice().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE"],
|
||||
&["CREATE"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IPOWER",
|
||||
alice(),
|
||||
TimelineEventType::RoomPowerLevels,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "users": { alice(): 100 } })).unwrap(),
|
||||
&["CREATE", "IMA"],
|
||||
&["IMA"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IJR",
|
||||
alice(),
|
||||
TimelineEventType::RoomJoinRules,
|
||||
Some(""),
|
||||
to_raw_json_value(&RoomJoinRulesEventContent::new(JoinRule::Public)).unwrap(),
|
||||
&["CREATE", "IMA", "IPOWER"],
|
||||
&["IPOWER"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMB",
|
||||
bob(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(bob().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE", "IJR", "IPOWER"],
|
||||
&["IJR"],
|
||||
),
|
||||
to_pdu_event(
|
||||
"IMC",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMember,
|
||||
Some(charlie().as_str()),
|
||||
member_content_join(),
|
||||
&["CREATE", "IJR", "IPOWER"],
|
||||
&["IMB"],
|
||||
),
|
||||
to_pdu_event::<&EventId>(
|
||||
"START",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMessage,
|
||||
Some("dummy"),
|
||||
to_raw_json_value(&json!({})).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
to_pdu_event::<&EventId>(
|
||||
"END",
|
||||
charlie(),
|
||||
TimelineEventType::RoomMessage,
|
||||
Some("dummy"),
|
||||
to_raw_json_value(&json!({})).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|ev| (ev.event_id().to_owned(), ev))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// all graphs start with these input events
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn INITIAL_EVENTS_CREATE_ROOM() -> HashMap<OwnedEventId, Pdu> {
|
||||
vec![to_pdu_event::<&EventId>(
|
||||
"CREATE",
|
||||
alice(),
|
||||
TimelineEventType::RoomCreate,
|
||||
Some(""),
|
||||
to_raw_json_value(&json!({ "creator": alice() })).unwrap(),
|
||||
&[],
|
||||
&[],
|
||||
)]
|
||||
.into_iter()
|
||||
.map(|ev| (ev.event_id().to_owned(), ev))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn INITIAL_EDGES() -> Vec<OwnedEventId> {
|
||||
vec!["START", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"]
|
||||
.into_iter()
|
||||
.map(event_id)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
+1
-1
@@ -35,7 +35,7 @@ pub use conduwuit_build_metadata as build_metadata;
|
||||
pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use info::version::*;
|
||||
pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res};
|
||||
pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu};
|
||||
pub use parking_lot::{
|
||||
Mutex as SyncMutex, RwLock as SyncRwLock, RwLockReadGuard as SyncRwLockReadGuard,
|
||||
RwLockWriteGuard as SyncRwLockWriteGuard,
|
||||
|
||||
@@ -53,4 +53,21 @@ impl super::Map {
|
||||
let key = ser::serialize(buf, key).expect("failed to serialize query key");
|
||||
self.get(key)
|
||||
}
|
||||
|
||||
pub fn qry_blocking<K>(self: &Arc<Self>, key: &K) -> Result<Handle<'_>>
|
||||
where
|
||||
K: Serialize + ?Sized + Debug,
|
||||
{
|
||||
let mut buf = KeyBuf::new();
|
||||
self.bqry_blocking(key, &mut buf)
|
||||
}
|
||||
|
||||
pub fn bqry_blocking<K, B>(self: &Arc<Self>, key: &K, buf: &mut B) -> Result<Handle<'_>>
|
||||
where
|
||||
K: Serialize + ?Sized + Debug,
|
||||
B: Write + AsRef<[u8]>,
|
||||
{
|
||||
let key = ser::serialize(buf, key).expect("failed to serialize query key");
|
||||
self.get_blocking(key)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::collections::{HashMap, hash_map};
|
||||
|
||||
use conduwuit::{Err, Event, EventTypeExt, PduEvent, Result, err, warn};
|
||||
use conduwuit::{
|
||||
Err, Event, EventTypeExt, PduEvent, Result, debug_info, err, utils::TryFutureExtExt, warn,
|
||||
};
|
||||
use ruma::{
|
||||
OwnedEventId, ServerName, api::federation::authorization::get_event_authorization,
|
||||
room_version_rules::RoomVersionRules,
|
||||
@@ -191,15 +193,8 @@ impl super::Service {
|
||||
},
|
||||
}
|
||||
}
|
||||
if !self
|
||||
.auth_state_check_4(
|
||||
&pdu,
|
||||
room_version_rules,
|
||||
create_event.as_pdu(),
|
||||
&auth_events_by_key,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Some(msg) = self.auth_state_check_4(&pdu, room_version_rules) {
|
||||
debug_info!("Rejecting auth chain event {event_id}: {msg}");
|
||||
self.reject_and_persist(&event_id, &pdu.to_canonical_object());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,18 +195,10 @@ impl super::Service {
|
||||
}
|
||||
}
|
||||
|
||||
if !self
|
||||
.auth_state_check_4(
|
||||
&pdu_event,
|
||||
&room_version_rules,
|
||||
create_event.as_pdu(),
|
||||
&auth_events_by_key,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if let Some(msg) = self.auth_state_check_4(&pdu_event, &room_version_rules) {
|
||||
self.reject_and_persist(event_id, &incoming_pdu);
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Event authorisation fails based on event's claimed auth events"
|
||||
"Event authorisation fails based on event's claimed auth events: {msg}"
|
||||
))));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,14 @@ use std::{collections::HashMap, fmt::Write, sync::Arc, time::Instant};
|
||||
|
||||
use assign::assign;
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Err, Error, Event, PduEvent, Result, Server, SyncRwLock, utils::MutexMap};
|
||||
use conduwuit::{
|
||||
Err, Error, Event, PduEvent, Result, Server, SyncRwLock,
|
||||
utils::{MutexMap, TryFutureExtExt},
|
||||
};
|
||||
pub use fetch_and_handle_outliers::{
|
||||
DagBuilderTree, GET_MISSING_EVENTS_MAX_BATCH_SIZE, build_local_dag,
|
||||
};
|
||||
use futures::future::ok;
|
||||
use http::StatusCode;
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
@@ -131,8 +135,8 @@ impl Service {
|
||||
}
|
||||
|
||||
/// Fetches a single PDU, returning None if there is an error.
|
||||
async fn event_fetch(&self, event_id: OwnedEventId) -> Option<PduEvent> {
|
||||
self.services.timeline.get_pdu(&event_id).await.ok()
|
||||
fn event_fetch(&self, event_id: &EventId) -> Option<PduEvent> {
|
||||
self.services.timeline.get_pdu_blocking(event_id).ok()
|
||||
}
|
||||
|
||||
/// Returns a rate-limit error if the requested event had a recent failed
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use base64::Engine;
|
||||
use conduwuit::{
|
||||
Err, Event, EventTypeExt, PduEvent, Result, debug, debug::DebugInspect, debug_error,
|
||||
debug_info, err, info, matrix::StateKey, state_res, trace,
|
||||
debug_info, err, info, matrix::StateKey, trace,
|
||||
};
|
||||
use futures::future::ready;
|
||||
use ruma::{
|
||||
@@ -12,6 +12,8 @@ use ruma::{
|
||||
canonical_json::redact,
|
||||
events::StateEventType,
|
||||
room_version_rules::{EventIdFormatVersion, RoomVersionRules},
|
||||
state_res,
|
||||
state_res::check_state_independent_auth_rules,
|
||||
};
|
||||
|
||||
use crate::rooms::{
|
||||
@@ -144,24 +146,21 @@ impl super::Service {
|
||||
/// Checks PDU check 4: Passes authorisation rules based on the event's auth
|
||||
/// events ([spec]).
|
||||
///
|
||||
/// If the auth check fails, false is returned, otherwise true.
|
||||
/// If the auth check fails, the error message is returned, otherwise
|
||||
/// `None`.
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/v1.19/server-server-api/#checks-performed-on-receipt-of-a-pdu
|
||||
pub async fn auth_state_check_4(
|
||||
pub fn auth_state_check_4(
|
||||
&self,
|
||||
pdu: &PduEvent,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event: &PduEvent,
|
||||
auth_events_by_key: &HashMap<(StateEventType, StateKey), PduEvent>,
|
||||
) -> Result<bool> {
|
||||
let state_fetch = |ty: &StateEventType, sk: &str| {
|
||||
let key = (ty.to_owned(), sk.into());
|
||||
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
|
||||
};
|
||||
|
||||
state_res::event_auth::auth_check(room_version_rules, pdu, state_fetch, create_event)
|
||||
.await
|
||||
.map_err(|e| err!("Event self-authentication failed: {e:?}"))
|
||||
) -> Option<String> {
|
||||
match check_state_independent_auth_rules(&room_version_rules.authorization, pdu, |e| {
|
||||
self.event_fetch(e)
|
||||
}) {
|
||||
| Ok(_) => None,
|
||||
| Err(msg) => Some(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the event passes PDU check 5, which ensures that the event
|
||||
@@ -177,7 +176,7 @@ impl super::Service {
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event: &PduEvent,
|
||||
origin: &ServerName,
|
||||
) -> Result<(bool, HashMap<u64, OwnedEventId>)> {
|
||||
) -> Result<(Option<String>, HashMap<u64, OwnedEventId>)> {
|
||||
debug!(
|
||||
event_id = %incoming_pdu.event_id,
|
||||
"Resolving state at event"
|
||||
@@ -218,29 +217,27 @@ impl super::Service {
|
||||
}
|
||||
trace!(state_events = state_before.len(), "Calculated incoming state");
|
||||
|
||||
let state_fetch_state = &state_before;
|
||||
let state_fetch = |k: StateEventType, s: StateKey| async move {
|
||||
let shortstatekey = self.services.short.get_shortstatekey(&k, &s).await.ok()?;
|
||||
|
||||
let event_id = state_fetch_state.get(&shortstatekey)?;
|
||||
self.services.timeline.get_pdu(event_id).await.ok()
|
||||
};
|
||||
|
||||
debug!(
|
||||
event_id = %incoming_pdu.event_id,
|
||||
"Running state-before auth check"
|
||||
);
|
||||
|
||||
// PDU check: 5
|
||||
let auth_check = state_res::event_auth::auth_check(
|
||||
room_version_rules,
|
||||
let state_fetch_state = &state_before;
|
||||
let auth_result = state_res::check_state_dependent_auth_rules(
|
||||
&room_version_rules.authorization,
|
||||
incoming_pdu,
|
||||
|ty, sk| state_fetch(ty.clone(), sk.into()),
|
||||
create_event.as_pdu(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
||||
Ok((auth_check, state_before))
|
||||
|event_type, state_key| {
|
||||
let ssk = self
|
||||
.services
|
||||
.short
|
||||
.get_shortstatekey_blocking(event_type, state_key)
|
||||
.ok()?;
|
||||
let event_id = state_fetch_state.get(&ssk)?;
|
||||
self.services.timeline.get_pdu_blocking(event_id).ok()
|
||||
},
|
||||
);
|
||||
Ok((auth_result.err(), state_before))
|
||||
}
|
||||
|
||||
/// Checks that the event passes PDU check 6, which ensures that the event
|
||||
@@ -253,8 +250,7 @@ impl super::Service {
|
||||
&self,
|
||||
incoming_pdu: &PduEvent,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
create_event: &PduEvent,
|
||||
) -> Result<bool> {
|
||||
) -> Result<(), String> {
|
||||
debug!(
|
||||
event_id = %incoming_pdu.event_id,
|
||||
"Gathering auth events"
|
||||
@@ -270,25 +266,22 @@ impl super::Service {
|
||||
incoming_pdu.content(),
|
||||
room_version_rules,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let state_fetch = |k: &StateEventType, s: &str| {
|
||||
let key = k.with_state_key(s);
|
||||
ready(auth_events.get(&key).map(ToOwned::to_owned))
|
||||
};
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
debug!(
|
||||
event_id = %incoming_pdu.event_id,
|
||||
"Running current state auth check"
|
||||
);
|
||||
state_res::event_auth::auth_check(
|
||||
room_version_rules,
|
||||
state_res::check_state_dependent_auth_rules(
|
||||
&room_version_rules.authorization,
|
||||
incoming_pdu,
|
||||
state_fetch,
|
||||
create_event.as_pdu(),
|
||||
|t, s| {
|
||||
auth_events
|
||||
.get(&(t.to_owned(), s.to_owned()))
|
||||
.map(ToOwned::to_owned)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))
|
||||
}
|
||||
|
||||
/// Performs PDU check 7 - does the policy server allow this event.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -9,13 +9,21 @@ use conduwuit::{
|
||||
state_res::{self, StateMap},
|
||||
trace,
|
||||
utils::stream::{IterStream, ReadyExt, TryWidebandExt, WidebandExt},
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join};
|
||||
use ruma::{OwnedEventId, RoomId, room_version_rules::RoomVersionRules};
|
||||
use itertools::cloned;
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, RoomId,
|
||||
room_version_rules::RoomVersionRules,
|
||||
state_res::{Event, utils::event_id_set::EventIdSet},
|
||||
};
|
||||
|
||||
use crate::rooms::state_compressor::CompressedState;
|
||||
|
||||
impl super::Service {
|
||||
/// Resolves the state based on the incoming fork states with the given room
|
||||
/// version rules.
|
||||
pub async fn resolve_state(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@@ -48,7 +56,7 @@ impl super::Service {
|
||||
.event_ids_iter(room_id, state.values().map(Borrow::borrow))
|
||||
.try_collect()
|
||||
})
|
||||
.try_collect::<Vec<HashSet<OwnedEventId>>>();
|
||||
.try_collect::<Vec<EventIdSet<OwnedEventId>>>();
|
||||
|
||||
let fork_states = fork_states
|
||||
.iter()
|
||||
@@ -60,19 +68,19 @@ impl super::Service {
|
||||
.short
|
||||
.multi_get_statekey_from_short(shortstatekeys)
|
||||
.zip(event_ids)
|
||||
.ready_filter_map(|(ty_sk, id)| Some((ty_sk.ok()?, id)))
|
||||
.ready_filter_map(|(ty_sk, id)| {
|
||||
Some((ty_sk.ok().map(|(k, s)| (k, s.to_string()))?, id))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.map(Ok::<_, Error>)
|
||||
.try_collect::<Vec<StateMap<OwnedEventId>>>();
|
||||
.try_collect::<Vec<ruma::state_res::StateMap<OwnedEventId>>>();
|
||||
|
||||
let (fork_states, auth_chain_sets) = try_join(fork_states, auth_chain_sets).await?;
|
||||
|
||||
trace!("Resolving state");
|
||||
let state = self
|
||||
.state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets)
|
||||
.boxed()
|
||||
.await?;
|
||||
let state =
|
||||
self.state_resolution(room_version_rules, fork_states.as_slice(), auth_chain_sets)?;
|
||||
|
||||
trace!("State resolution done.");
|
||||
let state_events: Vec<_> = state
|
||||
@@ -98,25 +106,72 @@ impl super::Service {
|
||||
Ok(Arc::new(new_room_state))
|
||||
}
|
||||
|
||||
pub async fn state_resolution<'a, StateSets>(
|
||||
/// Actually performs state resolution from the given state sets, auth chain
|
||||
/// sets, and room version rules.
|
||||
pub fn state_resolution<'a>(
|
||||
&'a self,
|
||||
room_version_rules: &'a RoomVersionRules,
|
||||
state_sets: StateSets,
|
||||
auth_chain_sets: &'a [HashSet<OwnedEventId>],
|
||||
) -> Result<StateMap<OwnedEventId>>
|
||||
where
|
||||
StateSets: Iterator<Item = &'a StateMap<OwnedEventId>> + Clone + Send,
|
||||
{
|
||||
state_sets: &[ruma::state_res::StateMap<OwnedEventId>],
|
||||
auth_chain_sets: Vec<EventIdSet<OwnedEventId>>,
|
||||
) -> Result<ruma::state_res::StateMap<OwnedEventId>> {
|
||||
let event_fetch = |event_id| self.event_fetch(event_id);
|
||||
let event_exists = |event_id| self.event_exists(event_id);
|
||||
state_res::resolve(
|
||||
room_version_rules,
|
||||
let calc_subgraph = |ss| self.calculate_conflicted_state_subgraph(ss);
|
||||
ruma::state_res::resolve(
|
||||
&room_version_rules.authorization,
|
||||
&room_version_rules
|
||||
.state_res
|
||||
.v2_rules()
|
||||
.expect("can only use state resolution v2 rules"),
|
||||
state_sets,
|
||||
auth_chain_sets,
|
||||
&event_fetch,
|
||||
&event_exists,
|
||||
event_fetch,
|
||||
calc_subgraph,
|
||||
)
|
||||
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
|
||||
.await
|
||||
.map_err(|e| err!(error!("state resolution failed: {e:?}")))
|
||||
}
|
||||
|
||||
/// Calculates the conflicted state subgraph for state resolution.
|
||||
fn calculate_conflicted_state_subgraph<'a>(
|
||||
&self,
|
||||
state_set: &ruma::state_res::StateMap<&'a EventId>,
|
||||
) -> Result<HashSet<&'a EventId>> {
|
||||
let conflicted_events: HashSet<_> = state_set.values().cloned().collect();
|
||||
let mut subgraph: HashSet<_> = HashSet::new();
|
||||
let mut stack: Vec<Vec<_>> = vec![conflicted_events.iter().cloned().collect::<Vec<_>>()];
|
||||
let mut path: Vec<_> = Vec::new();
|
||||
let mut seen: HashSet<_> = HashSet::new();
|
||||
let next_event = |stack: &mut Vec<Vec<_>>, path: &mut Vec<_>| {
|
||||
while stack.last().is_some_and(Vec::is_empty) {
|
||||
stack.pop();
|
||||
path.pop();
|
||||
}
|
||||
stack.last_mut().and_then(Vec::pop)
|
||||
};
|
||||
while let Some(event_id) = next_event(&mut stack, &mut path) {
|
||||
path.push(event_id.clone());
|
||||
if subgraph.contains(&event_id) {
|
||||
if path.len() > 1 {
|
||||
subgraph.extend(path.iter().cloned());
|
||||
}
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
if conflicted_events.contains(&event_id) && path.len() > 1 {
|
||||
subgraph.extend(path.iter().cloned());
|
||||
}
|
||||
if seen.contains(&event_id) {
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
trace!(event_id = event_id.as_str(), "fetching event for its auth events");
|
||||
let Some(evt) = self.event_fetch(event_id.clone()) else {
|
||||
warn!("could not fetch event {} to calculate conflicted subgraph", event_id);
|
||||
path.pop();
|
||||
continue;
|
||||
};
|
||||
stack.push(evt.auth_events().collect());
|
||||
seen.insert(event_id);
|
||||
}
|
||||
Ok(subgraph)
|
||||
}
|
||||
}
|
||||
@@ -65,14 +65,14 @@ impl super::Service {
|
||||
// perform PDU check 5 (event auth passes based on state before the
|
||||
// event). To do this, we either need to have all the prev events
|
||||
// locally, or ask a remote server for the state at the event.
|
||||
let (passes_state_before, state_before) = self
|
||||
let (state_before_failure_reason, state_before) = self
|
||||
.state_before_check_5(&incoming_pdu, &room_version_rules, create_event, origin)
|
||||
.await?;
|
||||
|
||||
if !passes_state_before {
|
||||
if let Some(msg) = state_before_failure_reason {
|
||||
self.reject_and_persist(incoming_pdu.event_id(), &val);
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Event authorisation fails based on the state before the event"
|
||||
"Event authorisation fails based on the state before the event: {msg}"
|
||||
))));
|
||||
}
|
||||
|
||||
@@ -90,16 +90,16 @@ impl super::Service {
|
||||
);
|
||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||
let passes_current_state = self
|
||||
.current_state_check_6(&incoming_pdu, &room_version_rules, create_event)
|
||||
.current_state_check_6(&incoming_pdu, &room_version_rules)
|
||||
.await
|
||||
.inspect(|passes| {
|
||||
if !*passes {
|
||||
debug_warn!(
|
||||
"Event authorisation fails based on the current room state - will be \
|
||||
soft-failed"
|
||||
);
|
||||
}
|
||||
})?;
|
||||
.inspect_err(|reason| {
|
||||
debug_warn!(
|
||||
%reason,
|
||||
"Event authorisation fails based on the current room state - will be \
|
||||
soft-failed"
|
||||
);
|
||||
})
|
||||
.is_ok();
|
||||
|
||||
// Determine whether this PDU should be soft-failed.
|
||||
// If the auth check failed, invariably yes. Otherwise, only if the user
|
||||
|
||||
@@ -154,6 +154,15 @@ impl Service {
|
||||
.deserialized()
|
||||
}
|
||||
|
||||
pub fn get_shortstatekey_blocking(
|
||||
&self,
|
||||
event_type: &StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<ShortStateKey> {
|
||||
let key = (event_type, state_key);
|
||||
self.db.shortstatekey_statekey.qry()
|
||||
}
|
||||
|
||||
/// Gets a full event ID from a short event ID.
|
||||
pub async fn get_eventid_from_short<Id>(&self, shorteventid: ShortEventId) -> Result<Id>
|
||||
where
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::{collections::HashMap, fmt::Write, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{debug, utils::stream::WidebandExt};
|
||||
use conduwuit::{debug, matrix::StateKey, smallstr::SmallString, utils::stream::WidebandExt};
|
||||
use conduwuit_core::{
|
||||
Event, PduEvent, Result, err,
|
||||
result::FlatOk,
|
||||
state_res::{self, StateMap},
|
||||
utils::{
|
||||
IterStream, MutexMap, MutexMapGuard, ReadyExt, calculate_hash,
|
||||
stream::{BroadbandExt, TryIgnore},
|
||||
@@ -21,6 +20,7 @@ use ruma::{
|
||||
api::federation::membership::RawStrippedState,
|
||||
events::{StateEventType, TimelineEventType, room::create::RoomCreateEventContent},
|
||||
room_version_rules::RoomVersionRules,
|
||||
state_res,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -472,7 +472,7 @@ impl Service {
|
||||
state_key: Option<&str>,
|
||||
content: &serde_json::value::RawValue,
|
||||
room_version_rules: &RoomVersionRules,
|
||||
) -> Result<StateMap<PduEvent>> {
|
||||
) -> Result<HashMap<(StateEventType, String), PduEvent>> {
|
||||
let Ok(shortstatehash) = self.get_room_shortstatehash(room_id).await else {
|
||||
return Ok(HashMap::new());
|
||||
};
|
||||
@@ -482,8 +482,9 @@ impl Service {
|
||||
sender,
|
||||
state_key,
|
||||
content,
|
||||
room_version_rules,
|
||||
)?;
|
||||
&room_version_rules.authorization,
|
||||
)
|
||||
.map_err(|e| err!("failed to select auth types for event: {e:?}"))?;
|
||||
debug!(?auth_types, "Auth types for event");
|
||||
let sauthevents: HashMap<_, _> = auth_types
|
||||
.iter()
|
||||
|
||||
@@ -161,6 +161,22 @@ impl Data {
|
||||
select_ok([accepted, outlier]).await.map(at!(0))
|
||||
}
|
||||
|
||||
pub(super) fn get_pdu_blocking(&self, event_id: &EventId) -> Result<PduEvent> {
|
||||
if let Ok(outlier) = self
|
||||
.eventid_outlierpdu
|
||||
.get_blocking(event_id)?
|
||||
.deserialized()
|
||||
{
|
||||
return Ok(outlier);
|
||||
};
|
||||
|
||||
let pdu_id = self
|
||||
.eventid_pduid
|
||||
.get_blocking(event_id)
|
||||
.map(|h| RawPduId::from(&*h))?;
|
||||
self.pduid_pdu.get_blocking(&pdu_id)?.deserialized()
|
||||
}
|
||||
|
||||
/// Like get_non_outlier_pdu(), but without the expense of fetching and
|
||||
/// parsing the PduEvent
|
||||
#[inline]
|
||||
|
||||
@@ -211,6 +211,12 @@ impl Service {
|
||||
self.db.get_pdu(event_id).await
|
||||
}
|
||||
|
||||
/// Returns the PDU. Checks the outlier tree BEFORE the timeline tree, as
|
||||
/// this operation is synchronous.
|
||||
pub fn get_pdu_blocking(&self, event_id: &EventId) -> Result<PduEvent> {
|
||||
self.db.get_pdu_blocking(event_id)
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// This does __NOT__ check the outliers `Tree`.
|
||||
|
||||
Reference in new issue
Block a user