larcen state-res from ruma

This commit is contained in:
Jason Volk 2025-02-05 12:22:22 +00:00 committed by strawberry
parent b872f8e593
commit 0a9a9b3c92
24 changed files with 5082 additions and 33 deletions

View file

@ -355,7 +355,6 @@ features = [
"federation-api",
"markdown",
"push-gateway-api-c",
"state-res",
"server-util",
"unstable-exhaustive-types",
"ring-compat",

View file

@ -12,7 +12,7 @@ use conduwuit::{
at, debug, debug_info, debug_warn, err, info,
pdu::{gen_event_id_canonical_json, PduBuilder},
result::FlatOk,
trace,
state_res, trace,
utils::{self, shuffle, IterStream, ReadyExt},
warn, Err, PduEvent, Result,
};
@ -40,8 +40,8 @@ use ruma::{
},
StateEventType,
},
state_res, CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId,
OwnedServerName, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
};
use service::{
appservice::RegistrationInfo,

View file

@ -11,7 +11,7 @@ use conduwuit::{
math::{ruma_from_usize, usize_from_ruma},
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
},
warn, Error, Result,
warn, Error, Result, TypeStateKey,
};
use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::{
@ -24,7 +24,6 @@ use ruma::{
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
},
serde::Raw,
state_res::TypeStateKey,
uint, DeviceId, OwnedEventId, OwnedRoomId, RoomId, UInt, UserId,
};
use service::{rooms::read_receipt::pack_receipts, PduCount};

View file

@ -121,7 +121,7 @@ pub enum Error {
#[error(transparent)]
Signatures(#[from] ruma::signatures::Error),
#[error(transparent)]
StateRes(#[from] ruma::state_res::Error),
StateRes(#[from] crate::state_res::Error),
#[error("uiaa")]
Uiaa(ruma::api::client::uiaa::UiaaInfo),

View file

@ -8,6 +8,7 @@ pub mod metrics;
pub mod mods;
pub mod pdu;
pub mod server;
pub mod state_res;
pub mod utils;
pub use ::arrayvec;
@ -22,6 +23,7 @@ pub use error::Error;
pub use info::{rustc_flags_capture, version, version::version};
pub use pdu::{Event, PduBuilder, PduCount, PduEvent, PduId, RawPduId, StateKey};
pub use server::Server;
pub use state_res::{EventTypeExt, RoomVersion, StateMap, TypeStateKey};
pub use utils::{ctor, dtor, implement, result, result::Result};
pub use crate as conduwuit_core;

View file

@ -1,8 +1,8 @@
pub use ruma::state_res::Event;
use ruma::{events::TimelineEventType, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId};
use serde_json::value::RawValue as RawJsonValue;
use super::Pdu;
pub use crate::state_res::Event;
impl Event for Pdu {
type Id = OwnedEventId;

View file

@ -0,0 +1,17 @@
//! 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.

View file

@ -0,0 +1,23 @@
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

1644
src/core/state_res/mod.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
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

View file

@ -0,0 +1,256 @@
use std::collections::BTreeMap;
use ruma::{
events::{room::power_levels::RoomPowerLevelsEventContent, TimelineEventType},
power_levels::{default_power_level, NotificationPowerLevels},
serde::{
deserialize_v1_powerlevel, vec_deserialize_int_powerlevel_values,
vec_deserialize_v1_powerlevel_values,
},
Int, OwnedUserId, UserId,
};
use serde::Deserialize;
use serde_json::{from_str as from_json_str, Error};
use tracing::error;
use super::{Result, RoomVersion};
#[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 From<IntRoomPowerLevelsEventContent> for RoomPowerLevelsEventContent {
fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self {
let IntRoomPowerLevelsEventContent {
ban,
events,
events_default,
invite,
kick,
redact,
state_default,
users,
users_default,
notifications,
} = int_pl;
let mut pl = Self::new();
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: &RoomVersion,
) -> Option<RoomPowerLevelsEventContent> {
if room_version.integer_power_levels {
deserialize_integer_power_levels(content)
} else {
deserialize_legacy_power_levels(content)
}
}
fn deserialize_integer_power_levels(content: &str) -> Option<RoomPowerLevelsEventContent> {
match from_json_str::<IntRoomPowerLevelsEventContent>(content) {
| Ok(content) => Some(content.into()),
| 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 }
}
}
#[inline]
pub(crate) fn deserialize_power_levels_content_fields(
content: &str,
room_version: &RoomVersion,
) -> Result<PowerLevelsContentFields, Error> {
if room_version.integer_power_levels {
deserialize_integer_power_levels_content_fields(content)
} else {
deserialize_legacy_power_levels_content_fields(content)
}
}
fn deserialize_integer_power_levels_content_fields(
content: &str,
) -> Result<PowerLevelsContentFields, Error> {
from_json_str::<IntPowerLevelsContentFields>(content).map(Into::into)
}
fn deserialize_legacy_power_levels_content_fields(
content: &str,
) -> Result<PowerLevelsContentFields, Error> {
from_json_str(content)
}
#[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: &RoomVersion,
) -> Result<PowerLevelsContentInvite, Error> {
if room_version.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: &RoomVersion,
) -> Result<PowerLevelsContentRedact, Error> {
if room_version.integer_power_levels {
from_json_str::<IntPowerLevelsContentRedact>(content).map(Into::into)
} else {
from_json_str(content)
}
}

View file

@ -0,0 +1,149 @@
use ruma::RoomVersionId;
use super::{Error, Result};
#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
pub enum RoomDisposition {
/// A room version that has a stable specification.
Stable,
/// A room version that is not yet fully specified.
Unstable,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
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,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum StateResolutionVersion {
/// State resolution for rooms at version 1.
V1,
/// State resolution for room at version 2 or later.
V2,
}
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct RoomVersion {
/// 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,
// FIXME: not sure what this one means?
pub enforce_key_validity: bool,
/// `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 canonical json, do not allow:
/// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
/// * Floats
/// * NaN, Infinity, -Infinity
pub strict_canonicaljson: bool,
/// 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,
/// Allow knocking in event authentication.
///
/// See [room v7 specification](https://spec.matrix.org/latest/rooms/v7/) for more information.
pub allow_knocking: bool,
/// Adds support for the restricted join rule.
///
/// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289) for more information.
pub restricted_join_rules: bool,
/// Adds support for the knock_restricted join rule.
///
/// See: [MSC3787](https://github.com/matrix-org/matrix-spec-proposals/pull/3787) for more information.
pub knock_restricted_join_rule: bool,
/// Enforces integer power levels.
///
/// See: [MSC3667](https://github.com/matrix-org/matrix-spec-proposals/pull/3667) for more information.
pub integer_power_levels: bool,
/// Determine the room creator using the `m.room.create` event's `sender`,
/// instead of the event content's `creator` field.
///
/// See: [MSC2175](https://github.com/matrix-org/matrix-spec-proposals/pull/2175) for more information.
pub use_room_create_sender: bool,
}
impl RoomVersion {
pub const V1: Self = Self {
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,
extra_redaction_checks: true,
allow_knocking: false,
restricted_join_rules: false,
knock_restricted_join_rule: false,
integer_power_levels: false,
use_room_create_sender: false,
};
pub const V10: Self = Self {
knock_restricted_join_rule: true,
integer_power_levels: true,
..Self::V9
};
pub const V11: Self = Self {
use_room_create_sender: true,
..Self::V10
};
pub const V2: Self = Self {
state_res: StateResolutionVersion::V2,
..Self::V1
};
pub const V3: Self = Self {
event_format: EventFormatVersion::V2,
extra_redaction_checks: false,
..Self::V2
};
pub const V4: Self = Self {
event_format: EventFormatVersion::V3,
..Self::V3
};
pub const V5: Self = Self { enforce_key_validity: true, ..Self::V4 };
pub const V6: Self = Self {
special_case_aliases_auth: false,
strict_canonicaljson: true,
limit_notifications_power_levels: true,
..Self::V5
};
pub const V7: Self = Self { allow_knocking: true, ..Self::V6 };
pub const V8: Self = Self { restricted_join_rules: true, ..Self::V7 };
pub const V9: Self = Self::V8;
pub fn new(version: &RoomVersionId) -> Result<Self> {
Ok(match version {
| RoomVersionId::V1 => Self::V1,
| RoomVersionId::V2 => Self::V2,
| RoomVersionId::V3 => Self::V3,
| RoomVersionId::V4 => Self::V4,
| RoomVersionId::V5 => Self::V5,
| RoomVersionId::V6 => Self::V6,
| RoomVersionId::V7 => Self::V7,
| RoomVersionId::V8 => Self::V8,
| RoomVersionId::V9 => Self::V9,
| RoomVersionId::V10 => Self::V10,
| RoomVersionId::V11 => Self::V11,
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))),
})
}
}

View file

@ -0,0 +1,102 @@
use std::{
borrow::Borrow,
fmt::{Debug, Display},
hash::Hash,
sync::Arc,
};
use ruma::{events::TimelineEventType, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId};
use serde_json::value::RawValue as RawJsonValue;
/// Abstraction of a PDU so users can have their own PDU types.
pub trait Event {
type Id: Clone + Debug + Display + Eq + Ord + Hash + Send + Borrow<EventId>;
/// The `EventId` of this event.
fn event_id(&self) -> &Self::Id;
/// 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) -> MilliSecondsSinceUnixEpoch;
/// The event type.
fn event_type(&self) -> &TimelineEventType;
/// The event's content.
fn content(&self) -> &RawJsonValue;
/// The state key for this event.
fn state_key(&self) -> Option<&str>;
/// The events before this event.
// Requires GATs to avoid boxing (and TAIT for making it convenient).
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_;
/// All the authenticating events for this event.
// Requires GATs to avoid boxing (and TAIT for making it convenient).
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_;
/// If this event is a redaction event this is the event it redacts.
fn redacts(&self) -> Option<&Self::Id>;
}
impl<T: Event> Event for &T {
type Id = T::Id;
fn event_id(&self) -> &Self::Id { (*self).event_id() }
fn room_id(&self) -> &RoomId { (*self).room_id() }
fn sender(&self) -> &UserId { (*self).sender() }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { (*self).origin_server_ts() }
fn event_type(&self) -> &TimelineEventType { (*self).event_type() }
fn content(&self) -> &RawJsonValue { (*self).content() }
fn state_key(&self) -> Option<&str> { (*self).state_key() }
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
(*self).prev_events()
}
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
(*self).auth_events()
}
fn redacts(&self) -> Option<&Self::Id> { (*self).redacts() }
}
impl<T: Event> Event for Arc<T> {
type Id = T::Id;
fn event_id(&self) -> &Self::Id { (**self).event_id() }
fn room_id(&self) -> &RoomId { (**self).room_id() }
fn sender(&self) -> &UserId { (**self).sender() }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { (**self).origin_server_ts() }
fn event_type(&self) -> &TimelineEventType { (**self).event_type() }
fn content(&self) -> &RawJsonValue { (**self).content() }
fn state_key(&self) -> Option<&str> { (**self).state_key() }
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
(**self).prev_events()
}
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
(**self).auth_events()
}
fn redacts(&self) -> Option<&Self::Id> { (**self).redacts() }
}

View file

@ -0,0 +1,648 @@
// Because of criterion `cargo bench` works,
// but if you use `cargo bench -- --save-baseline <name>`
// 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 <name of the bench> -- --save-baseline <name>`.
#![allow(clippy::exhaustive_structs)]
use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
sync::{
atomic::{AtomicU64, Ordering::SeqCst},
Arc,
},
};
use criterion::{criterion_group, criterion_main, Criterion};
use event::PduEvent;
use futures::{future, future::ready};
use ruma::{int, uint};
use maplit::{btreemap, hashmap, hashset};
use ruma::{
room_id, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId,
Signatures, UserId,
};
use ruma::events::{
pdu::{EventHash, Pdu, RoomV3Pdu},
room::{
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
StateEventType, TimelineEventType,
};
use conduwuit::state_res::{self as state_res, Error, Event, Result, StateMap};
use serde_json::{
json,
value::{to_raw_value as to_raw_json_value, RawValue as RawJsonValue},
};
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
fn lexico_topo_sort(c: &mut Criterion) {
c.bench_function("lexicographical topological sort", |b| {
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")],
};
b.iter(|| {
let _ = state_res::lexicographical_topological_sort(&graph, &|_| {
future::ok((int!(0), MilliSecondsSinceUnixEpoch(uint!(0))))
});
});
});
}
fn resolution_shallow_auth_chain(c: &mut Criterion) {
c.bench_function("resolve state of 5 events one fork", |b| {
let mut store = TestStore(hashmap! {});
// build up the DAG
let (state_at_bob, state_at_charlie, _) = store.set_up();
b.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(Arc::clone));
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
let auth_chain_sets = 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}"),
};
});
});
}
fn resolve_deeper_event_set(c: &mut Criterion) {
c.bench_function("resolve state of 10 events 3 conflicting", |b| {
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().with_state_key(ev.state_key().unwrap()), 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().with_state_key(ev.state_key().unwrap()), ev.event_id().to_owned())
})
.collect::<StateMap<_>>();
b.iter(|| async {
let state_sets = [&state_set_a, &state_set_b];
let auth_chain_sets = 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(Arc::clone));
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"),
};
});
});
}
criterion_group!(
benches,
lexico_topo_sort,
resolution_shallow_auth_chain,
resolve_deeper_event_set
);
criterion_main!(benches);
//*/////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION DETAILS AHEAD
//
/////////////////////////////////////////////////////////////////////*/
struct TestStore<E: Event>(HashMap<OwnedEventId, Arc<E>>);
#[allow(unused)]
impl<E: Event> TestStore<E> {
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<Arc<E>> {
self.0
.get(event_id)
.map(Arc::clone)
.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<Arc<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<E::Id>) -> Result<HashSet<E::Id>> {
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<E::Id>>) -> Result<Vec<E::Id>> {
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.borrow()))
.collect())
} else {
Ok(vec![])
}
}
}
impl TestStore<PduEvent> {
#[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(), Arc::clone(&create_event));
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(), Arc::clone(&alice_mem));
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(|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)
}
}
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],
) -> Arc<PduEvent>
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<_>>();
let state_key = state_key.map(ToOwned::to_owned);
Arc::new(PduEvent {
event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu {
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()),
state_key,
kind: ev_type,
content,
redacts: None,
unsigned: btreemap! {},
auth_events,
prev_events,
depth: uint!(0),
hashes: EventHash::new(String::new()),
signatures: Signatures::new(),
}),
})
}
// all graphs start with these input events
#[allow(non_snake_case)]
fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Arc<PduEvent>> {
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, Arc<PduEvent>> {
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()
}
/// Convenience trait for adding event type plus state key to state maps.
trait EventTypeExt {
fn with_state_key(self, state_key: impl Into<String>) -> (StateEventType, String);
}
impl EventTypeExt for &TimelineEventType {
fn with_state_key(self, state_key: impl Into<String>) -> (StateEventType, String) {
(self.to_string().into(), state_key.into())
}
}
mod event {
use ruma_common::{MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId};
use ruma_events::{pdu::Pdu, TimelineEventType};
use ruma_state_res::Event;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
impl Event for PduEvent {
type Id = OwnedEventId;
fn event_id(&self) -> &Self::Id {
&self.event_id
}
fn room_id(&self) -> &RoomId {
match &self.rest {
Pdu::RoomV1Pdu(ev) => &ev.room_id,
Pdu::RoomV3Pdu(ev) => &ev.room_id,
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn sender(&self) -> &UserId {
match &self.rest {
Pdu::RoomV1Pdu(ev) => &ev.sender,
Pdu::RoomV3Pdu(ev) => &ev.sender,
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn event_type(&self) -> &TimelineEventType {
match &self.rest {
Pdu::RoomV1Pdu(ev) => &ev.kind,
Pdu::RoomV3Pdu(ev) => &ev.kind,
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn content(&self) -> &RawJsonValue {
match &self.rest {
Pdu::RoomV1Pdu(ev) => &ev.content,
Pdu::RoomV3Pdu(ev) => &ev.content,
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
match &self.rest {
Pdu::RoomV1Pdu(ev) => ev.origin_server_ts,
Pdu::RoomV3Pdu(ev) => ev.origin_server_ts,
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn state_key(&self) -> Option<&str> {
match &self.rest {
Pdu::RoomV1Pdu(ev) => ev.state_key.as_deref(),
Pdu::RoomV3Pdu(ev) => ev.state_key.as_deref(),
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
match &self.rest {
Pdu::RoomV1Pdu(ev) => Box::new(ev.prev_events.iter().map(|(id, _)| id)),
Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter()),
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
match &self.rest {
Pdu::RoomV1Pdu(ev) => Box::new(ev.auth_events.iter().map(|(id, _)| id)),
Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter()),
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
fn redacts(&self) -> Option<&Self::Id> {
match &self.rest {
Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
#[cfg(not(feature = "unstable-exhaustive-types"))]
_ => unreachable!("new PDU version"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct PduEvent {
pub(crate) event_id: OwnedEventId,
#[serde(flatten)]
pub(crate) rest: Pdu,
}
}

View file

@ -0,0 +1,688 @@
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap, HashSet},
sync::{
atomic::{AtomicU64, Ordering::SeqCst},
Arc,
},
};
use futures_util::future::ready;
use js_int::{int, uint};
use ruma_common::{
event_id, room_id, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId,
RoomVersionId, ServerSignatures, UserId,
};
use ruma_events::{
pdu::{EventHash, Pdu, RoomV3Pdu},
room::{
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
TimelineEventType,
};
use serde_json::{
json,
value::{to_raw_value as to_raw_json_value, RawValue as RawJsonValue},
};
use tracing::info;
pub(crate) use self::event::PduEvent;
use crate::{auth_types_for_event, Error, Event, EventTypeExt, Result, StateMap};
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
pub(crate) async fn do_check(
events: &[Arc<PduEvent>],
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 -> PduEvent
let mut event_map: HashMap<OwnedEventId, Arc<PduEvent>> = 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 crate::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: <PduEvent as Event>::Id| ready(event_map.get(&id).cloned());
let exists = |id: <PduEvent as Event>::Id| ready(event_map.get(&id).is_some());
let resolved = crate::resolve(
&RoomVersionId::V6,
state_sets,
&auth_chain_sets,
&fetch,
&exists,
1,
)
.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(),
)
.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(), 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).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".to_owned())
})
.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, Arc<E>>);
impl<E: Event> TestStore<E> {
pub(crate) fn get_event(&self, _: &RoomId, event_id: &EventId) -> Result<Arc<E>> {
self.0
.get(event_id)
.cloned()
.ok_or_else(|| Error::NotFound(format!("{event_id} not found")))
}
/// 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<E::Id>,
) -> Result<HashSet<E::Id>> {
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<PduEvent> {
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(), Arc::clone(&create_event));
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(), Arc::clone(&alice_mem));
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>,
) -> Arc<PduEvent> {
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
let id = if id.contains('$') {
id.to_owned()
} else {
format!("${id}:foo")
};
let state_key = state_key.map(ToOwned::to_owned);
Arc::new(PduEvent {
event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu {
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()),
state_key,
kind: ev_type,
content,
redacts: None,
unsigned: BTreeMap::new(),
auth_events: vec![],
prev_events: vec![],
depth: uint!(0),
hashes: EventHash::new("".to_owned()),
signatures: ServerSignatures::default(),
}),
})
}
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],
) -> Arc<PduEvent>
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<_>>();
let state_key = state_key.map(ToOwned::to_owned);
Arc::new(PduEvent {
event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu {
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()),
state_key,
kind: ev_type,
content,
redacts: None,
unsigned: BTreeMap::new(),
auth_events,
prev_events,
depth: uint!(0),
hashes: EventHash::new("".to_owned()),
signatures: ServerSignatures::default(),
}),
})
}
// all graphs start with these input events
#[allow(non_snake_case)]
pub(crate) fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Arc<PduEvent>> {
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, Arc<PduEvent>> {
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<_>>()
}
pub(crate) mod event {
use ruma_common::{MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId};
use ruma_events::{pdu::Pdu, TimelineEventType};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::Event;
impl Event for PduEvent {
type Id = OwnedEventId;
fn event_id(&self) -> &Self::Id { &self.event_id }
fn room_id(&self) -> &RoomId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.room_id,
| Pdu::RoomV3Pdu(ev) => &ev.room_id,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn sender(&self) -> &UserId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.sender,
| Pdu::RoomV3Pdu(ev) => &ev.sender,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn event_type(&self) -> &TimelineEventType {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.kind,
| Pdu::RoomV3Pdu(ev) => &ev.kind,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn content(&self) -> &RawJsonValue {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.content,
| Pdu::RoomV3Pdu(ev) => &ev.content,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.origin_server_ts,
| Pdu::RoomV3Pdu(ev) => ev.origin_server_ts,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn state_key(&self) -> Option<&str> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.state_key.as_deref(),
| Pdu::RoomV3Pdu(ev) => ev.state_key.as_deref(),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
#[allow(refining_impl_trait)]
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => Box::new(ev.prev_events.iter().map(|(id, _)| id)),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter()),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
#[allow(refining_impl_trait)]
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => Box::new(ev.auth_events.iter().map(|(id, _)| id)),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter()),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn redacts(&self) -> Option<&Self::Id> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[allow(clippy::exhaustive_structs)]
pub(crate) struct PduEvent {
pub(crate) event_id: OwnedEventId,
#[serde(flatten)]
pub(crate) rest: Pdu,
}
}

View file

@ -3,12 +3,15 @@ use std::{
sync::Arc,
};
use conduwuit::{debug_warn, err, implement, PduEvent, Result};
use conduwuit::{
debug_warn, err, implement,
state_res::{self},
PduEvent, Result,
};
use futures::{future, FutureExt};
use ruma::{
int,
state_res::{self},
uint, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, ServerName, UInt,
int, uint, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, ServerName,
UInt,
};
use super::check_room_id;

View file

@ -3,10 +3,12 @@ use std::{
sync::Arc,
};
use conduwuit::{debug, debug_info, err, implement, trace, warn, Err, Error, PduEvent, Result};
use conduwuit::{
debug, debug_info, err, implement, state_res, trace, warn, Err, Error, PduEvent, Result,
};
use futures::{future::ready, TryFutureExt};
use ruma::{
api::client::error::ErrorKind, events::StateEventType, state_res, CanonicalJsonObject,
api::client::error::ErrorKind, events::StateEventType, CanonicalJsonObject,
CanonicalJsonValue, EventId, RoomId, ServerName,
};

View file

@ -19,12 +19,12 @@ use std::{
use conduwuit::{
utils::{MutexMap, TryFutureExtExt},
Err, PduEvent, Result, Server,
Err, PduEvent, Result, RoomVersion, Server,
};
use futures::TryFutureExt;
use ruma::{
events::room::create::RoomCreateEventContent, state_res::RoomVersion, OwnedEventId,
OwnedRoomId, RoomId, RoomVersionId,
events::room::create::RoomCreateEventContent, OwnedEventId, OwnedRoomId, RoomId,
RoomVersionId,
};
use crate::{globals, rooms, sending, server_keys, Dep};

View file

@ -5,15 +5,14 @@ use std::{
};
use conduwuit::{
err, implement, trace,
err, implement,
state_res::{self, StateMap},
trace,
utils::stream::{automatic_width, IterStream, ReadyExt, TryWidebandExt, WidebandExt},
Error, Result,
};
use futures::{future::try_join, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use ruma::{
state_res::{self, StateMap},
OwnedEventId, RoomId, RoomVersionId,
};
use ruma::{OwnedEventId, RoomId, RoomVersionId};
use crate::rooms::state_compressor::CompressedState;

View file

@ -8,10 +8,10 @@ use std::{
use conduwuit::{
debug, err, implement, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt},
PduEvent, Result,
PduEvent, Result, StateMap,
};
use futures::{future::try_join, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use ruma::{state_res::StateMap, OwnedEventId, RoomId, RoomVersionId};
use ruma::{OwnedEventId, RoomId, RoomVersionId};
use crate::rooms::short::ShortStateHash;

View file

@ -1,16 +1,12 @@
use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant};
use conduwuit::{
debug, debug_info, err, implement, trace,
debug, debug_info, err, implement, state_res, trace,
utils::stream::{BroadbandExt, ReadyExt},
warn, Err, PduEvent, Result,
warn, Err, EventTypeExt, PduEvent, Result,
};
use futures::{future::ready, FutureExt, StreamExt};
use ruma::{
events::StateEventType,
state_res::{self, EventTypeExt},
CanonicalJsonValue, RoomId, ServerName,
};
use ruma::{events::StateEventType, CanonicalJsonValue, RoomId, ServerName};
use super::{get_room_version_id, to_room_version};
use crate::rooms::{

View file

@ -3,6 +3,7 @@ use std::{collections::HashMap, fmt::Write, iter::once, sync::Arc};
use conduwuit::{
err,
result::FlatOk,
state_res::{self, StateMap},
utils::{
calculate_hash,
stream::{BroadbandExt, TryIgnore},
@ -20,7 +21,6 @@ use ruma::{
AnyStrippedStateEvent, StateEventType, TimelineEventType,
},
serde::Raw,
state_res::{self, StateMap},
EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId,
};

View file

@ -12,6 +12,7 @@ use std::{
use conduwuit::{
at, debug, debug_warn, err, error, implement, info,
pdu::{gen_event_id, EventHash, PduBuilder, PduCount, PduEvent},
state_res::{self, Event, RoomVersion},
utils::{
self, future::TryExtExt, stream::TryIgnore, IterStream, MutexMap, MutexMapGuard, ReadyExt,
},
@ -36,7 +37,6 @@ use ruma::{
GlobalAccountDataEventType, StateEventType, TimelineEventType,
},
push::{Action, Ruleset, Tweak},
state_res::{self, Event, RoomVersion},
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedServerName, RoomId, RoomVersionId, ServerName, UserId,
};