State-reset and security mitigations.

Upgrade Ruma to present.

The following are intentionally benign for activation in a later commit:

- Hydra backports not default.
- Room version 12 not default.
- Room version 12 not listed as stable.

Do not enable them manually or you can brick your database.

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2025-06-29 03:33:29 +00:00
parent 2c6dd78502
commit 628597c318
134 changed files with 14961 additions and 4935 deletions

View File

@@ -0,0 +1,214 @@
[
{
"test-comments": [
"NOTE: Unlike the v11 pdus, alice is never in `m.room.power_levels`.",
"This is due to MSC4289 forbidding room creators from being in the",
"`users` field of `m.room.power_levels`."
],
"event_id": "$00-m-room-create",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"room_version": "12"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-create"
],
"auth_events": []
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-alice"
],
"auth_events": [
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-power_levels"
],
"auth_events": [
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!00-m-room-create",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-join_rules"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!00-m-room-create",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-bob"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-join_rules",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "invite"
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-charlie"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-leave-alice",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "leave"
},
"state_key": "@alice:example.com",
"origin_server_ts": 7,
"depth": 7,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-join_rules"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-change-display-name-bob",
"room_id": "!00-m-room-create",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob++",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 8,
"depth": 8,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-member-leave-alice"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-member-join-bob",
"$01-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-charlie",
"room_id": "!00-m-room-create",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie++",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-member-leave-alice"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-member-join-charlie",
"$01-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,223 @@
[
{
"event_id": "$00-m-room-create",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"room_version": "11"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-create"
],
"auth_events": [
"$00-m-room-create"
]
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-alice"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-join_rules"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-bob"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "invite"
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-charlie"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-leave-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "leave"
},
"state_key": "@alice:example.com",
"origin_server_ts": 7,
"depth": 7,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-join_rules"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-change-display-name-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob++",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 8,
"depth": 8,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-member-leave-alice"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-bob",
"$01-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie++",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-member-leave-alice"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-charlie",
"$01-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,8 @@
[
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-charlie",
"$01-m-room-join_rules",
"$01-m-room-member-leave-alice",
"$01-m-room-member-change-display-name-bob"
]

View File

@@ -0,0 +1,8 @@
[
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules",
"$00-m-room-member-join-bob",
"$01-m-room-member-leave-alice",
"$01-m-room-member-change-display-name-charlie"
]

View File

@@ -0,0 +1,239 @@
[
{
"test-comments": [
"NOTE: Unlike the v11 pdus, alice is never in `m.room.power_levels`.",
"This is due to MSC4289 forbidding room creators from being in the",
"`users` field of `m.room.power_levels`."
],
"event_id": "$00-m-room-create",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"room_version": "12"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-create"
],
"auth_events": []
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-alice"
],
"auth_events": [
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-power_levels"
],
"auth_events": [
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!00-m-room-create",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-join_rules"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!00-m-room-create",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-bob"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-power_levels",
"room_id": "!00-m-room-create",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@bob:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-charlie"
],
"auth_events": [
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$02-m-room-power_levels",
"room_id": "!00-m-room-create",
"sender": "@bob:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@bob:example.com": 50,
"@charlie:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 7,
"depth": 7,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-power_levels"
],
"auth_events": [
"$01-m-room-power_levels",
"$00-m-room-member-join-bob"
]
},
{
"event_id": "$00-m-room-member-join-zara",
"room_id": "!00-m-room-create",
"sender": "@zara:example.com",
"type": "m.room.member",
"content": {
"displayname": "zara",
"membership": "join"
},
"state_key": "@zara:example.com",
"origin_server_ts": 8,
"depth": 8,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$02-m-room-power_levels"
],
"auth_events": [
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-eve",
"room_id": "!00-m-room-create",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$02-m-room-power_levels"
],
"auth_events": [
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-eve",
"room_id": "!00-m-room-create",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve++",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-eve"
],
"auth_events": [
"$02-m-room-power_levels",
"$00-m-room-member-join-eve",
"$00-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,251 @@
[
{
"event_id": "$00-m-room-create",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"room_version": "11"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-create"
],
"auth_events": [
"$00-m-room-create"
]
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-alice"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-join_rules"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-bob"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100,
"@bob:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-charlie"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$02-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100,
"@bob:example.com": 50,
"@charlie:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 7,
"depth": 7,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$01-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$01-m-room-power_levels",
"$00-m-room-member-join-bob"
]
},
{
"event_id": "$00-m-room-member-join-zara",
"room_id": "!room:example.com",
"sender": "@zara:example.com",
"type": "m.room.member",
"content": {
"displayname": "zara",
"membership": "join"
},
"state_key": "@zara:example.com",
"origin_server_ts": 8,
"depth": 8,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$02-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-eve",
"room_id": "!room:example.com",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$02-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-eve",
"room_id": "!room:example.com",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve++",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"signatures": {},
"hashes": {"sha256": ""},
"prev_events": [
"$00-m-room-member-join-eve"
],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-member-join-eve",
"$00-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,9 @@
[
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels",
"$00-m-room-join_rules",
"$00-m-room-member-join-bob",
"$00-m-room-member-join-charlie",
"$01-m-room-member-change-display-name-eve"
]

View File

@@ -0,0 +1,9 @@
[
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-join_rules",
"$00-m-room-member-join-bob",
"$00-m-room-member-join-charlie",
"$02-m-room-power_levels",
"$00-m-room-member-join-zara"
]

View File

@@ -0,0 +1,129 @@
[
{
"event_id": "$00-m-room-create",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"creator": "@alice:example.com",
"room_version": "10"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-create"
],
"auth_events": [
"$00-m-room-create"
]
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-member-join-alice"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "invite"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-power_levels"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-history_visibility",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.history_visibility",
"content": {
"history_visibility": "shared"
},
"state_key": "",
"origin_server_ts": 4,
"depth": 4,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-join_rules"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-guest_access",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.guest_access",
"content": {
"guest_access": "can_join"
},
"state_key": "",
"origin_server_ts": 5,
"depth": 5,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-history_visibility"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
}
]

View File

@@ -0,0 +1,64 @@
[
{
"test-comments": [
"NOTE: It is very important that the `event_id` of this PDU is ",
"lexicographically larger than the `event_id` of the following PDU, to ",
"ensure that the tiebreaking is done by the `origin_server_ts` field ",
"and not by the `event_id` field."
],
"event_id": "$02-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "restricted",
"allow": [
{
"room_id": "!other:example.com",
"type": "m.room_membership"
}
]
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-guest_access"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"test-comments": [
"NOTE: It is very important that the `event_id` of this PDU is ",
"lexicographically smaller than the `event_id` of the previous PDU, to ",
"ensure that the tiebreaking is done by the `origin_server_ts` field ",
"and not by the `event_id` field."
],
"event_id": "$01-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 7,
"depth": 7,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [
"$00-m-room-guest_access"
],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
}
]

View File

@@ -0,0 +1,232 @@
[
{
"event_id": "$00-m-room-create",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"creator": "@alice:example.com",
"room_version": "10"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-create"],
"auth_events": [
"$00-m-room-create"
]
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-alice"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-power_levels"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-join_rules"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-bob"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100,
"@bob:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-charlie"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$02-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100,
"@bob:example.com": 50,
"@charlie:example.com": 50
}
},
"state_key": "",
"origin_server_ts": 7,
"depth": 7,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$01-m-room-power_levels"],
"auth_events": [
"$00-m-room-create",
"$01-m-room-power_levels",
"$00-m-room-member-join-bob"
]
},
{
"event_id": "$00-m-room-member-join-zara",
"room_id": "!room:example.com",
"sender": "@zara:example.com",
"type": "m.room.member",
"content": {
"displayname": "zara",
"membership": "join"
},
"state_key": "@zara:example.com",
"origin_server_ts": 8,
"depth": 8,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$02-m-room-power_levels"],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-eve",
"room_id": "!room:example.com",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$02-m-room-power_levels"],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-eve",
"room_id": "!room:example.com",
"sender": "@eve:example.com",
"type": "m.room.member",
"content": {
"displayname": "eve++",
"membership": "join"
},
"state_key": "@eve:example.com",
"origin_server_ts": 9,
"depth": 9,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-eve"],
"auth_events": [
"$00-m-room-create",
"$02-m-room-power_levels",
"$00-m-room-member-join-eve",
"$00-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,206 @@
[
{
"event_id": "$00-m-room-create",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.create",
"content": {
"creator": "@alice:example.com",
"room_version": "10"
},
"state_key": "",
"origin_server_ts": 0,
"depth": 0,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": [],
"auth_events": []
},
{
"event_id": "$00-m-room-member-join-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "join"
},
"state_key": "@alice:example.com",
"origin_server_ts": 1,
"depth": 1,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-create"],
"auth_events": [
"$00-m-room-create"
]
},
{
"event_id": "$00-m-room-power_levels",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
},
"state_key": "",
"origin_server_ts": 2,
"depth": 2,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-alice"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$00-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "public"
},
"state_key": "",
"origin_server_ts": 3,
"depth": 3,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-power_levels"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-member-join-alice",
"$00-m-room-power_levels"
]
},
{
"event_id": "$00-m-room-member-join-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 4,
"depth": 4,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-join_rules"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$00-m-room-member-join-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 5,
"depth": 5,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-bob"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-join_rules",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.join_rules",
"content": {
"join_rule": "invite"
},
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$00-m-room-member-join-charlie"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-leave-alice",
"room_id": "!room:example.com",
"sender": "@alice:example.com",
"type": "m.room.member",
"content": {
"displayname": "alice",
"membership": "leave"
},
"state_key": "@alice:example.com",
"origin_server_ts": 7,
"depth": 7,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$01-m-room-join_rules"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-alice"
]
},
{
"event_id": "$01-m-room-member-change-display-name-bob",
"room_id": "!room:example.com",
"sender": "@bob:example.com",
"type": "m.room.member",
"content": {
"displayname": "bob++",
"membership": "join"
},
"state_key": "@bob:example.com",
"origin_server_ts": 8,
"depth": 8,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$01-m-room-member-leave-alice"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-bob",
"$01-m-room-join_rules"
]
},
{
"event_id": "$01-m-room-member-change-display-name-charlie",
"room_id": "!room:example.com",
"sender": "@charlie:example.com",
"type": "m.room.member",
"content": {
"displayname": "charlie++",
"membership": "join"
},
"state_key": "@charlie:example.com",
"origin_server_ts": 9,
"depth": 9,
"hashes": {"sha256": "aaa"},
"signatures": {},
"prev_events": ["$01-m-room-member-leave-alice"],
"auth_events": [
"$00-m-room-create",
"$00-m-room-power_levels",
"$00-m-room-member-join-charlie",
"$01-m-room-join_rules"
]
}
]

View File

@@ -0,0 +1,3 @@
//! Integration tests entrypoint.
mod resolve;

View File

@@ -0,0 +1,700 @@
//! State resolution integration tests.
#![cfg(test)]
use std::{
cmp::Ordering,
collections::{BTreeSet, HashMap},
error::Error,
fs,
path::Path,
};
use ruma::{
OwnedEventId, RoomVersionId,
events::{StateEventType, TimelineEventType},
room_version_rules::{AuthorizationRules, RoomVersionRules, StateResolutionV2Rules},
};
use serde::{Deserialize, Serialize};
use serde_json::{
Error as JsonError, Value as JsonValue, from_str as from_json_str,
to_string_pretty as to_json_string_pretty, to_value as to_json_value,
};
use similar::{Algorithm, udiff::unified_diff};
use tracing_subscriber::EnvFilter;
use tuwunel_core::{
Result, err,
matrix::{
Event, Pdu, StateKey, StateMap,
state_res::{AuthSet, resolve},
},
utils::stream::IterStream,
};
/// Create a new snapshot test.
///
/// # Arguments
///
/// * The test function's name.
/// * A list of JSON files relative to `tests/it/fixtures` to load PDUs to
/// resolve from.
macro_rules! snapshot_test {
($name:ident, $paths:expr $(,)?) => {
#[tokio::test]
async fn $name() {
let crate::resolve::Snapshots {
resolved_state,
} = crate::resolve::test_resolve(&$paths).await;
insta::with_settings!({
description => "Resolved state",
omit_expression => true,
snapshot_suffix => "resolved_state",
}, {
insta::assert_json_snapshot!(&resolved_state);
});
}
};
}
/// Create a new snapshot test, attempting to resolve multiple contrived states.
///
/// # Arguments
///
/// * The test function's name.
/// * A list of JSON files relative to `tests/it/fixtures` to load PDUs to
/// resolve from.
/// * A list of JSON files relative to `tests/it/fixtures` to load event IDs
/// forming contrived states to resolve.
macro_rules! snapshot_test_contrived_states {
($name:ident, $pdus_path:expr, $state_set_paths:expr $(,)?) => {
#[tokio::test]
async fn $name() {
let crate::resolve::Snapshots {
resolved_state,
} = crate::resolve::test_contrived_states(&$pdus_path, &$state_set_paths).await;
insta::with_settings!({
description => "Resolved state",
omit_expression => true,
snapshot_suffix => "resolved_state",
}, {
insta::assert_json_snapshot!(&resolved_state);
});
}
};
}
// This module must be defined lexically after the `snapshot_test` macro.
mod snapshot_tests;
/// Extract `.content.room_version` from a PDU.
#[derive(Deserialize)]
struct ExtractRoomVersion {
room_version: RoomVersionId,
}
/// Type describing a resolved state event.
#[derive(Serialize)]
struct ResolvedStateEvent {
kind: StateEventType,
state_key: StateKey,
event_id: OwnedEventId,
// Ignored in `PartialEq` and `Ord` because we don't want to consider it while sorting.
content: JsonValue,
}
impl PartialEq for ResolvedStateEvent {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.state_key == other.state_key
&& self.event_id == other.event_id
}
}
impl Eq for ResolvedStateEvent {}
impl Ord for ResolvedStateEvent {
fn cmp(&self, other: &Self) -> Ordering {
Ordering::Equal
.then(self.kind.cmp(&other.kind))
.then(self.state_key.cmp(&other.state_key))
.then(self.event_id.cmp(&other.event_id))
}
}
impl PartialOrd for ResolvedStateEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
/// Information to be captured in snapshot assertions
struct Snapshots {
/// The resolved state of the room.
resolved_state: BTreeSet<ResolvedStateEvent>,
}
fn snapshot_test_prelude(
paths: &[&str],
) -> (Vec<Vec<Pdu>>, RoomVersionRules, AuthorizationRules, StateResolutionV2Rules) {
// Run `cargo test -- --show-output` to view traces, set `RUST_LOG` to control
// filtering.
let subscriber = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_test_writer()
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)
.finish();
tracing::subscriber::set_global_default(subscriber).ok();
let fixtures_path = Path::new("tests/it/fixtures");
let pdu_batches = paths
.iter()
.map(|x| {
from_json_str(
&fs::read_to_string(fixtures_path.join(x))
.expect("should be able to read JSON file of PDUs"),
)
.expect("should be able to deserialize JSON file of PDUs")
})
.collect::<Vec<Vec<Pdu>>>();
let room_version_id = {
let first_pdu = pdu_batches
.first()
.expect("there should be at least one file of PDUs")
.first()
.expect("there should be at least one PDU in the first file");
assert_eq!(
first_pdu.kind,
TimelineEventType::RoomCreate,
"the first PDU in the first file should be an m.room.create event",
);
from_json_str::<ExtractRoomVersion>(first_pdu.content.get())
.expect("the m.room.create PDU's content should be valid")
.room_version
};
let rules = room_version_id
.rules()
.expect("room version should be supported");
let auth_rules = rules.clone().authorization;
let state_res_rules = rules
.state_res
.v2_rules()
.copied()
.expect("resolve only supports state resolution version 2");
(pdu_batches, rules, auth_rules, state_res_rules)
}
/// Reshape the data a bit to make the diff and snapshots easier to compare.
fn reshape(
pdus_by_id: &HashMap<OwnedEventId, Pdu>,
x: StateMap<OwnedEventId>,
) -> Result<BTreeSet<ResolvedStateEvent>, JsonError> {
x.into_iter()
.map(|((kind, state_key), event_id)| {
Ok(ResolvedStateEvent {
kind,
state_key,
content: to_json_value(pdus_by_id[&event_id].content())?,
event_id,
})
})
.collect()
}
/// Test a list of JSON files containing a list of PDUs and return the results.
///
/// State resolution is run both atomically for all PDUs and in batches of PDUs
/// by file.
async fn test_resolve(paths: &[&str]) -> Snapshots {
let (pdu_batches, rules, auth_rules, state_res_rules) = snapshot_test_prelude(paths);
// Resolve PDUs iteratively, using the ordering of `prev_events`.
let iteratively_resolved_state = resolve_iteratively(
&rules,
&auth_rules,
&state_res_rules,
pdu_batches.iter().flat_map(|x| x.iter()),
)
.await
.expect("iterative state resolution should succeed");
// Resolve PDUs in batches by file
let mut pdus_by_id = HashMap::new();
let mut batched_resolved_state = None;
for pdus in &pdu_batches {
batched_resolved_state = Some(
resolve_batch(
&rules,
&auth_rules,
&state_res_rules,
pdus,
&mut pdus_by_id,
&mut batched_resolved_state,
)
.await
.expect("batched state resolution step should succeed"),
);
}
let batched_resolved_state =
batched_resolved_state.expect("batched state resolution should have run at least once");
// Resolve all PDUs in a single step
let atomic_resolved_state = resolve_batch(
&rules,
&auth_rules,
&state_res_rules,
pdu_batches.iter().flat_map(|x| x.iter()),
&mut HashMap::new(),
&mut None,
)
.await
.expect("atomic state resolution should succeed");
let iteratively_resolved_state = reshape(&pdus_by_id, iteratively_resolved_state)
.expect("should be able to reshape iteratively resolved state");
let batched_resolved_state = reshape(&pdus_by_id, batched_resolved_state)
.expect("should be able to reshape batched resolved state");
let atomic_resolved_state = reshape(&pdus_by_id, atomic_resolved_state)
.expect("should be able to reshape atomic resolved state");
let assert_states_match = |first_resolved_state: &BTreeSet<ResolvedStateEvent>,
second_resolved_state: &BTreeSet<ResolvedStateEvent>,
first_name: &str,
second_name: &str| {
if first_resolved_state != second_resolved_state {
let diff = unified_diff(
Algorithm::default(),
&to_json_string_pretty(first_resolved_state)
.expect("should be able to serialize first resolved state"),
&to_json_string_pretty(second_resolved_state)
.expect("should be able to serialize second resolved state"),
3,
Some((first_name, second_name)),
);
panic!(
"{first_name} and {second_name} results should match; but they differ:\n{diff}"
);
}
};
assert_states_match(
&iteratively_resolved_state,
&batched_resolved_state,
"iterative",
"batched",
);
assert_states_match(&batched_resolved_state, &atomic_resolved_state, "batched", "atomic");
Snapshots {
resolved_state: iteratively_resolved_state,
}
}
/// Test a list of JSON files containing a list of PDUs and a list of JSON files
/// containing the event IDs that form a contrived state and return the results.
#[tracing::instrument(parent = None, name = "test", skip_all)]
async fn test_contrived_states(pdus_paths: &[&str], state_sets_paths: &[&str]) -> Snapshots {
let (pdu_batches, rules, _auth_rules, _state_res_rules) = snapshot_test_prelude(pdus_paths);
let pdus = pdu_batches
.into_iter()
.flat_map(IntoIterator::into_iter)
.collect::<Vec<_>>();
let pdus_by_id: HashMap<OwnedEventId, Pdu> = pdus
.clone()
.into_iter()
.map(|pdu| (pdu.event_id().to_owned(), pdu.clone()))
.collect();
let fixtures_path = Path::new("tests/it/fixtures");
let state_sets = state_sets_paths
.iter()
.map(|x| {
from_json_str::<Vec<OwnedEventId>>(
&fs::read_to_string(fixtures_path.join(x))
.expect("should be able to read JSON file of PDUs"),
)
.expect("should be able to deserialize JSON file of PDUs")
.into_iter()
.map(|event_id| {
pdus_by_id
.get(&event_id)
.map(|pdu| {
(
(
pdu.event_type().to_string().into(),
pdu.state_key
.clone()
.expect("All PDUs must be state events"),
),
event_id,
)
})
.expect("Event IDs in JSON file must be in PDUs JSON")
})
.collect()
})
.collect::<Vec<StateMap<OwnedEventId>>>();
let mut auth_chain_sets = Vec::new();
for state_map in &state_sets {
let mut auth_chain = AuthSet::new();
for event_id in state_map.values() {
let pdu = pdus_by_id
.get(event_id)
.expect("We already confirmed all state set event ids have pdus");
auth_chain.extend(
auth_events_dfs(&pdus_by_id, pdu).expect("Auth events DFS should not fail"),
);
}
auth_chain_sets.push(auth_chain);
}
let exists = async |x| pdus_by_id.contains_key(&x);
let fetch = async |x| {
pdus_by_id
.get(&x)
.cloned()
.ok_or_else(|| err!(Request(NotFound("event not found"))))
};
let resolved_state = resolve(
&rules,
state_sets.into_iter().stream(),
auth_chain_sets.into_iter().stream(),
&fetch,
&exists,
false,
)
.await
.expect("atomic state resolution should succeed");
Snapshots {
resolved_state: reshape(&pdus_by_id, resolved_state)
.expect("should be able to reshape atomic resolved state"),
}
}
/// Perform state resolution on a batch of PDUs.
///
/// This function can be used to resolve the state of a room in a single call if
/// all PDUs are provided at once, or across multiple calls if given PDUs in
/// batches in a loop. The latter form simulates the case commonly experienced
/// by homeservers during normal operation.
///
/// # Arguments
///
/// * `rules`: The rules of the room version.
/// * `pdus`: An iterator of [`Pdu`]s to resolve, either alone or against the
/// `prev_state`.
/// * `pdus_by_id`: A map of [`OwnedEventId`]s to the [`Pdu`] with that ID.
/// * Should be empty for the first call.
/// * Should not be mutated outside of this function.
/// * `prev_state`: The state returned by a previous call to this function, if
/// any.
/// * Should be `None` for the first call.
/// * Should not be mutated outside of this function.
async fn resolve_batch<'a, I, II>(
rules: &'a RoomVersionRules,
_auth_rules: &'a AuthorizationRules,
_state_res_rules: &'a StateResolutionV2Rules,
pdus: II,
pdus_by_id: &'a mut HashMap<OwnedEventId, Pdu>,
prev_state: &'a mut Option<StateMap<OwnedEventId>>,
) -> Result<StateMap<OwnedEventId>, Box<dyn Error>>
where
I: Iterator<Item = &'a Pdu> + Send + 'a,
II: IntoIterator<IntoIter = I> + Clone + Send + 'a,
Pdu: Send + Sync + 'a,
&'a Pdu: Send + 'a,
{
let mut state_sets = prev_state
.take()
.map(|x| vec![x])
.unwrap_or_default();
for pdu in pdus.clone() {
// Insert each state event into its own StateMap because we don't know any valid
// groupings.
let mut state_map = StateMap::new();
state_map.insert(
(
pdu.event_type().to_string().into(),
pdu.state_key()
.ok_or("all PDUs should be state events")?
.into(),
),
pdu.event_id().to_owned(),
);
state_sets.push(state_map);
}
pdus_by_id.extend(
pdus.clone()
.into_iter()
.map(|pdu| (pdu.event_id().to_owned(), pdu.to_owned())),
);
let mut auth_chain_sets = Vec::new();
for pdu in pdus {
auth_chain_sets.push(auth_events_dfs(&*pdus_by_id, pdu)?);
}
let fetch = async |x| {
pdus_by_id
.get(&x)
.cloned()
.ok_or_else(|| err!(Request(NotFound("event not found"))))
};
let exists = async |x| pdus_by_id.contains_key(&x);
resolve(
rules,
state_sets.into_iter().stream(),
auth_chain_sets.into_iter().stream(),
&fetch,
&exists,
false,
)
.await
.map_err(Into::into)
}
/// Perform state resolution on a batch of PDUs iteratively, one-by-one.
///
/// This function walks the `prev_events` of each PDU forward, resolving each
/// pdu against the state(s) of it's `prev_events`, to emulate what would happen
/// in a regular room a server is participating in.
///
/// # Arguments
///
/// * `auth_rules`: The authorization rules of the room version.
/// * `state_res_rules`: The state resolution rules of the room version.
/// * `pdus`: An iterator of [`Pdu`]s to resolve, with the following
/// assumptions:
/// * `prev_events` of each PDU points to another provided state event.
///
/// # Returns
///
/// The state resolved by resolving all the leaves (PDUs which don't have any
/// other PDUs pointing to it via `prev_events`).
async fn resolve_iteratively<'a, I, II>(
rules: &'a RoomVersionRules,
_auth_rules: &'a AuthorizationRules,
_state_res_rules: &'a StateResolutionV2Rules,
pdus: II,
) -> Result<StateMap<OwnedEventId>, Box<dyn Error>>
where
I: Iterator<Item = &'a Pdu>,
II: IntoIterator<IntoIter = I> + Clone,
{
let mut forward_prev_events_graph: HashMap<OwnedEventId, Vec<_>> = HashMap::new();
let mut stack = Vec::new();
for pdu in pdus.clone() {
let mut has_prev_events = false;
for prev_event in pdu.prev_events() {
forward_prev_events_graph
.entry(prev_event.into())
.or_default()
.push(pdu.event_id().into());
has_prev_events = true;
}
if pdu.event_type() == &TimelineEventType::RoomCreate && !has_prev_events {
stack.push(pdu.event_id().to_owned());
}
}
let pdus_by_id: HashMap<OwnedEventId, Pdu> = pdus
.clone()
.into_iter()
.map(|pdu| (pdu.event_id().to_owned(), pdu.to_owned()))
.collect();
let exists = async |x| pdus_by_id.contains_key(&x);
let fetch = async |x| {
pdus_by_id
.get(&x)
.cloned()
.ok_or_else(|| err!(Request(NotFound("event not found"))))
};
let mut state_at_events: HashMap<OwnedEventId, StateMap<OwnedEventId>> = HashMap::new();
let mut leaves = Vec::new();
'outer: while let Some(event_id) = stack.pop() {
let mut states_before_event = Vec::new();
let mut auth_chain_sets = Vec::new();
let current_pdu = pdus_by_id
.get(&event_id)
.expect("every pdu should be available");
for prev_event in current_pdu.prev_events() {
let Some(state_at_event) = state_at_events.get(prev_event) else {
// State for a prev event is not known, we will come back to this event on a
// later iteration.
continue 'outer;
};
for pdu in state_at_event.values().map(|event_id| {
pdus_by_id
.get(event_id)
.expect("every pdu should be available")
}) {
auth_chain_sets.push(auth_events_dfs(&pdus_by_id, pdu)?);
}
states_before_event.push(state_at_event.clone());
}
if states_before_event.is_empty() {
// initial event, nothing to resolve
state_at_events.insert(
event_id.clone(),
StateMap::from_iter([(
(
current_pdu.event_type().to_string().into(),
current_pdu
.state_key()
.expect("all pdus are state events")
.into(),
),
event_id.clone(),
)]),
);
} else {
let state_before_event = resolve(
rules,
states_before_event.clone().into_iter().stream(),
auth_chain_sets.clone().into_iter().stream(),
&fetch,
&exists,
false,
)
.await?;
let mut proposed_state_at_event = state_before_event.clone();
proposed_state_at_event.insert(
(
current_pdu.event_type().to_string().into(),
current_pdu
.state_key()
.expect("all pdus are state events")
.into(),
),
event_id.clone(),
);
auth_chain_sets.push(auth_events_dfs(&pdus_by_id, current_pdu)?);
let state_at_event = resolve(
rules,
[state_before_event, proposed_state_at_event]
.into_iter()
.stream(),
auth_chain_sets.into_iter().stream(),
&fetch,
&exists,
false,
)
.await?;
state_at_events.insert(event_id.clone(), state_at_event);
}
if let Some(prev_events) = forward_prev_events_graph.get(&event_id) {
stack.extend(prev_events.iter().cloned());
} else {
// pdu is a leaf: no `prev_events` point to it.
leaves.push(event_id);
}
}
assert!(
state_at_events.len() == pdus_by_id.len(),
"Not all events have a state calculated! This is likely due to an event having a \
`prev_events` which points to a non-existent PDU."
);
let mut leaf_states = Vec::new();
let mut auth_chain_sets = Vec::new();
for leaf in leaves {
let state_at_event = state_at_events
.get(&leaf)
.expect("states at all events are known");
for pdu in state_at_event.values().map(|event_id| {
pdus_by_id
.get(event_id)
.expect("every pdu should be available")
}) {
auth_chain_sets.push(auth_events_dfs(&pdus_by_id, pdu)?);
}
leaf_states.push(state_at_event.clone());
}
resolve(
rules,
leaf_states.into_iter().stream(),
auth_chain_sets.into_iter().stream(),
&fetch,
&exists,
false,
)
.await
.map_err(Into::into)
}
/// Depth-first search for the `auth_events` of the given PDU.
///
/// # Errors
///
/// Fails if `pdus` does not contain a PDU that appears in the recursive
/// `auth_events` of `pdu`.
fn auth_events_dfs(
pdus_by_id: &HashMap<OwnedEventId, Pdu>,
pdu: &Pdu,
) -> Result<AuthSet<OwnedEventId>, Box<dyn Error>> {
let mut out = AuthSet::new();
let mut stack = pdu
.auth_events()
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
while let Some(event_id) = stack.pop() {
if out.contains(&event_id) {
continue;
}
out.insert(event_id.clone());
stack.extend(
pdus_by_id
.get(&event_id)
.ok_or_else(|| format!("missing required PDU: {event_id}"))?
.auth_events()
.map(ToOwned::to_owned),
);
}
Ok(out)
}

View File

@@ -0,0 +1,56 @@
//! Snapshot tests.
// Test the minimal set of events required to create a room with the
// "private_chat" preset.
snapshot_test!(minimal_private_chat, ["bootstrap-private-chat.json"]);
// Start with a private room, then transition its join rules to restricted, then
// to public. The events in the second file are tied topologically, so they must
// have the tiebreaking algorithm applied. The ordering should be decided by
// the `origin_server_ts` fields of these events, not the `event_id` fields. The
// power levels of these events are equivalent, so they don't really matter.
snapshot_test!(origin_server_ts_tiebreak, [
"bootstrap-private-chat.json",
"origin-server-ts-tiebreak.json"
],);
// Test that state res v2.0 is implemented starting from the unconflicted set,
// and NOT the empty set, leading to there being no join rules state.
//
// This example comes directly from the "Problem A" section of MSC4297.
snapshot_test_contrived_states!(
msc4297_problem_a_state_res_v2_0,
["MSC4297-problem-A/pdus-v11.json"],
["MSC4297-problem-A/state-bob.json", "MSC4297-problem-A/state-charlie.json"]
);
// Test that state res v2.1 is implemented starting from the empty set, and NOT
// the unconflicted set.
//
// This example comes directly from the "Problem A" section of MSC4297.
snapshot_test_contrived_states!(
msc4297_problem_a_state_res_v2_1,
["MSC4297-problem-A/pdus-hydra.json"],
["MSC4297-problem-A/state-bob.json", "MSC4297-problem-A/state-charlie.json"]
);
// Test that state res v2.0 does NOT consider the conflicted state subgraph as
// part of the full conflicted state set, leading to the state resetting to the
// first power levels event.
//
// This example comes directly from the "Problem B" section of MSC4297.
snapshot_test_contrived_states!(
msc4297_problem_b_state_res_v2_0,
["MSC4297-problem-B/pdus-v11.json"],
["MSC4297-problem-B/state-eve.json", "MSC4297-problem-B/state-zara.json"]
);
// Test that state res v2.1 considers the conflicted state subgraph as part of
// the full conflicted state set.
//
// This example comes directly from the "Problem B" section of MSC4297.
snapshot_test_contrived_states!(
msc4297_problem_b_state_res_v2_1,
["MSC4297-problem-B/pdus-hydra.json"],
["MSC4297-problem-B/state-eve.json", "MSC4297-problem-B/state-zara.json"]
);

View File

@@ -0,0 +1,58 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"creator": "@alice:example.com",
"room_version": "10"
}
},
{
"kind": "m.room.guest_access",
"state_key": "",
"event_id": "$00-m-room-guest_access",
"content": {
"guest_access": "can_join"
}
},
{
"kind": "m.room.history_visibility",
"state_key": "",
"event_id": "$00-m-room-history_visibility",
"content": {
"history_visibility": "shared"
}
},
{
"kind": "m.room.join_rules",
"state_key": "",
"event_id": "$00-m-room-join_rules",
"content": {
"join_rule": "invite"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$00-m-room-member-join-alice",
"content": {
"displayname": "alice",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$00-m-room-power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
}
}
]

View File

@@ -0,0 +1,51 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"room_version": "11"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$01-m-room-member-leave-alice",
"content": {
"displayname": "alice",
"membership": "leave"
}
},
{
"kind": "m.room.member",
"state_key": "@bob:example.com",
"event_id": "$01-m-room-member-change-display-name-bob",
"content": {
"displayname": "bob++",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@charlie:example.com",
"event_id": "$01-m-room-member-change-display-name-charlie",
"content": {
"displayname": "charlie++",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$00-m-room-power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
}
}
]

View File

@@ -0,0 +1,55 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"room_version": "12"
}
},
{
"kind": "m.room.join_rules",
"state_key": "",
"event_id": "$01-m-room-join_rules",
"content": {
"join_rule": "invite"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$01-m-room-member-leave-alice",
"content": {
"displayname": "alice",
"membership": "leave"
}
},
{
"kind": "m.room.member",
"state_key": "@bob:example.com",
"event_id": "$01-m-room-member-change-display-name-bob",
"content": {
"displayname": "bob++",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@charlie:example.com",
"event_id": "$01-m-room-member-change-display-name-charlie",
"content": {
"displayname": "charlie++",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$00-m-room-power_levels",
"content": {}
}
]

View File

@@ -0,0 +1,77 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"room_version": "11"
}
},
{
"kind": "m.room.join_rules",
"state_key": "",
"event_id": "$00-m-room-join_rules",
"content": {
"join_rule": "public"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$00-m-room-member-join-alice",
"content": {
"displayname": "alice",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@bob:example.com",
"event_id": "$00-m-room-member-join-bob",
"content": {
"displayname": "bob",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@charlie:example.com",
"event_id": "$00-m-room-member-join-charlie",
"content": {
"displayname": "charlie",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@eve:example.com",
"event_id": "$01-m-room-member-change-display-name-eve",
"content": {
"displayname": "eve++",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@zara:example.com",
"event_id": "$00-m-room-member-join-zara",
"content": {
"displayname": "zara",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$00-m-room-power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
}
}
]

View File

@@ -0,0 +1,78 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"room_version": "12"
}
},
{
"kind": "m.room.join_rules",
"state_key": "",
"event_id": "$00-m-room-join_rules",
"content": {
"join_rule": "public"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$00-m-room-member-join-alice",
"content": {
"displayname": "alice",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@bob:example.com",
"event_id": "$00-m-room-member-join-bob",
"content": {
"displayname": "bob",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@charlie:example.com",
"event_id": "$00-m-room-member-join-charlie",
"content": {
"displayname": "charlie",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@eve:example.com",
"event_id": "$01-m-room-member-change-display-name-eve",
"content": {
"displayname": "eve++",
"membership": "join"
}
},
{
"kind": "m.room.member",
"state_key": "@zara:example.com",
"event_id": "$00-m-room-member-join-zara",
"content": {
"displayname": "zara",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$02-m-room-power_levels",
"content": {
"users": {
"@bob:example.com": 50,
"@charlie:example.com": 50
}
}
}
]

View File

@@ -0,0 +1,58 @@
---
source: crates/ruma-state-res/tests/it/resolve/snapshot_tests.rs
description: Resolved state
---
[
{
"kind": "m.room.create",
"state_key": "",
"event_id": "$00-m-room-create",
"content": {
"creator": "@alice:example.com",
"room_version": "10"
}
},
{
"kind": "m.room.guest_access",
"state_key": "",
"event_id": "$00-m-room-guest_access",
"content": {
"guest_access": "can_join"
}
},
{
"kind": "m.room.history_visibility",
"state_key": "",
"event_id": "$00-m-room-history_visibility",
"content": {
"history_visibility": "shared"
}
},
{
"kind": "m.room.join_rules",
"state_key": "",
"event_id": "$01-m-room-join_rules",
"content": {
"join_rule": "public"
}
},
{
"kind": "m.room.member",
"state_key": "@alice:example.com",
"event_id": "$00-m-room-member-join-alice",
"content": {
"displayname": "alice",
"membership": "join"
}
},
{
"kind": "m.room.power_levels",
"state_key": "",
"event_id": "$00-m-room-power_levels",
"content": {
"users": {
"@alice:example.com": 100
}
}
}
]