mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c90216d37f | |||
| 6e16a6ef8f | |||
| 0870c8d647 | |||
| d0f00e6f5c | |||
| 5d44653e3a | |||
| 44e60d0ea6 | |||
| d7514178ab | |||
| 1d45e0b68c | |||
| 3c44dccd65 | |||
| b57be072c7 | |||
| ea5dc8e09d | |||
| b9d60c64e5 | |||
| 94ae824149 | |||
| 640714922b | |||
| 2b268fdaf3 | |||
| e8d823a653 | |||
| 0ba77674c7 | |||
| 2ccbd7d60b | |||
| 60960c6e09 | |||
| ce40304667 | |||
| dcbc4b54c5 | |||
| fce024b30b | |||
| 3e4e696761 | |||
| f605913ea9 | |||
| 44302ce732 | |||
| bfb0a2b76a | |||
| fcd5669aa1 |
@@ -19,11 +19,20 @@ outputs:
|
|||||||
rustc_version:
|
rustc_version:
|
||||||
description: The rustc version installed
|
description: The rustc version installed
|
||||||
value: ${{ steps.rustc-version.outputs.version }}
|
value: ${{ steps.rustc-version.outputs.version }}
|
||||||
|
rustup_version:
|
||||||
|
description: The rustup version installed
|
||||||
|
value: ${{ steps.rustup-version.outputs.version }}
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check if rustup is already installed
|
||||||
|
shell: bash
|
||||||
|
id: rustup-version
|
||||||
|
run: |
|
||||||
|
echo "version=$(rustup --version)" >> $GITHUB_OUTPUT
|
||||||
- name: Cache rustup toolchains
|
- name: Cache rustup toolchains
|
||||||
|
if: steps.rustup-version.outputs.version == ''
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -33,6 +42,7 @@ runs:
|
|||||||
# Requires repo to be cloned if toolchain is not specified
|
# Requires repo to be cloned if toolchain is not specified
|
||||||
key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
|
key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
|
if: steps.rustup-version.outputs.version == ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if ! command -v rustup &> /dev/null ; then
|
if ! command -v rustup &> /dev/null ; then
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ jobs:
|
|||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
runs-on: dind
|
runs-on: dind
|
||||||
container: ghcr.io/catthehacker/ubuntu:act-latest
|
|
||||||
needs: define-variables
|
needs: define-variables
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -181,14 +180,14 @@ jobs:
|
|||||||
file: "docker/Dockerfile"
|
file: "docker/Dockerfile"
|
||||||
build-args: |
|
build-args: |
|
||||||
GIT_COMMIT_HASH=${{ github.sha }})
|
GIT_COMMIT_HASH=${{ github.sha }})
|
||||||
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }})
|
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
|
||||||
GIT_REMOTE_URL=${{github.event.repository.html_url }}
|
GIT_REMOTE_URL=${{github.event.repository.html_url }}
|
||||||
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
|
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
sbom: true
|
sbom: true
|
||||||
outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true
|
||||||
env:
|
env:
|
||||||
@@ -211,7 +210,6 @@ jobs:
|
|||||||
|
|
||||||
merge:
|
merge:
|
||||||
runs-on: dind
|
runs-on: dind
|
||||||
container: ghcr.io/catthehacker/ubuntu:act-latest
|
|
||||||
needs: [define-variables, build-image]
|
needs: [define-variables, build-image]
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ jobs:
|
|||||||
-D warnings
|
-D warnings
|
||||||
|
|
||||||
- name: Show sccache stats
|
- name: Show sccache stats
|
||||||
|
if: always()
|
||||||
run: sccache --show-stats
|
run: sccache --show-stats
|
||||||
|
|
||||||
cargo-test:
|
cargo-test:
|
||||||
@@ -137,4 +138,5 @@ jobs:
|
|||||||
--no-fail-fast
|
--no-fail-fast
|
||||||
|
|
||||||
- name: Show sccache stats
|
- name: Show sccache stats
|
||||||
|
if: always()
|
||||||
run: sccache --show-stats
|
run: sccache --show-stats
|
||||||
|
|||||||
Generated
+209
-287
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -21,7 +21,7 @@ license = "Apache-2.0"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||||
rust-version = "1.86.0"
|
rust-version = "1.86.0"
|
||||||
version = "0.5.0-rc.5"
|
version = "0.5.0-rc.6"
|
||||||
|
|
||||||
[workspace.metadata.crane]
|
[workspace.metadata.crane]
|
||||||
name = "conduwuit"
|
name = "conduwuit"
|
||||||
|
|||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
# Security Policy for Continuwuity
|
||||||
|
|
||||||
|
This document outlines the security policy for Continuwuity. Our goal is to maintain a secure platform for all users, and we take security matters seriously.
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
We provide security updates for the following versions of Continuwuity:
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| -------------- |:----------------:|
|
||||||
|
| Latest release | ✅ |
|
||||||
|
| Main branch | ✅ |
|
||||||
|
| Older releases | ❌ |
|
||||||
|
|
||||||
|
We may backport fixes to the previous release at our discretion, but we don't guarantee this.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
### Responsible Disclosure
|
||||||
|
|
||||||
|
We appreciate the efforts of security researchers and the community in identifying and reporting vulnerabilities. To ensure that potential vulnerabilities are addressed properly, please follow these guidelines:
|
||||||
|
|
||||||
|
1. **Contact members of the team directly** over E2EE private message.
|
||||||
|
- [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link)
|
||||||
|
- [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) <!-- ? -->
|
||||||
|
2. **Email the security team** at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details.
|
||||||
|
3. **Do not disclose the vulnerability publicly** until it has been addressed
|
||||||
|
4. **Provide detailed information** about the vulnerability, including:
|
||||||
|
- A clear description of the issue
|
||||||
|
- Steps to reproduce
|
||||||
|
- Potential impact
|
||||||
|
- Any possible mitigations
|
||||||
|
- Version(s) affected, including specific commits if possible
|
||||||
|
|
||||||
|
If you have any doubts about a potential security vulnerability, contact us via private channels first! We'd prefer that you bother us, instead of having a vulnerability disclosed without a fix.
|
||||||
|
|
||||||
|
### What to Expect
|
||||||
|
|
||||||
|
When you report a security vulnerability:
|
||||||
|
|
||||||
|
1. **Acknowledgment**: We will acknowledge receipt of your report.
|
||||||
|
2. **Assessment**: We will assess the vulnerability and determine its impact on our users
|
||||||
|
3. **Updates**: We will provide updates on our progress in addressing the vulnerability, and may request you help test mitigations
|
||||||
|
4. **Resolution**: Once resolved, we will notify you and discuss coordinated disclosure
|
||||||
|
5. **Credit**: We will recognize your contribution (unless you prefer to remain anonymous)
|
||||||
|
|
||||||
|
## Security Update Process
|
||||||
|
|
||||||
|
When security vulnerabilities are identified:
|
||||||
|
|
||||||
|
1. We will develop and test fixes in a private fork
|
||||||
|
2. Security updates will be released as soon as possible
|
||||||
|
3. Release notes will include information about the vulnerabilities, avoiding details that could facilitate exploitation where possible
|
||||||
|
4. Critical security updates may be backported to the previous stable release
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Matrix Security Disclosure Policy](https://matrix.org/security-disclosure-policy/)
|
||||||
|
- [Continuwuity Documentation](https://continuwuity.org/introduction)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This security policy was last updated on May 25, 2025.
|
||||||
+15
-5
@@ -1641,19 +1641,29 @@
|
|||||||
#
|
#
|
||||||
#server =
|
#server =
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
# URL to a support page for the server, which will be served as part of
|
||||||
|
# the MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||||
|
# Will be included alongside any contact information
|
||||||
#
|
#
|
||||||
#support_page =
|
#support_page =
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
# Role string for server support contacts, to be served as part of the
|
||||||
|
# MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||||
#
|
#
|
||||||
#support_role =
|
#support_role = "m.role.admin"
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
# Email address for server support contacts, to be served as part of the
|
||||||
|
# MSC1929 server support endpoint.
|
||||||
|
# This will be used along with support_mxid if specified.
|
||||||
#
|
#
|
||||||
#support_email =
|
#support_email =
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
# Matrix ID for server support contacts, to be served as part of the
|
||||||
|
# MSC1929 server support endpoint.
|
||||||
|
# This will be used along with support_email if specified.
|
||||||
|
#
|
||||||
|
# If no email or mxid is specified, all of the server's admins will be
|
||||||
|
# listed.
|
||||||
#
|
#
|
||||||
#support_mxid =
|
#support_mxid =
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
# Continuwuity Database Mermaid Diagrams
|
||||||
|
|
||||||
|
This document contains visual representations of the Continuwuity database schema using Mermaid diagrams.
|
||||||
|
|
||||||
|
## 1. Core Event Storage Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Matrix Event ID<br/>48 bytes] --> B[eventid_shorteventid]
|
||||||
|
B --> C[Short Event ID<br/>8 bytes]
|
||||||
|
C --> D[shorteventid_eventid]
|
||||||
|
D --> A
|
||||||
|
|
||||||
|
A --> E[eventid_pduid]
|
||||||
|
E --> F[PDU ID<br/>16 bytes]
|
||||||
|
F --> G[pduid_pdu<br/>Main Event Storage]
|
||||||
|
|
||||||
|
A --> H[eventid_outlierpdu<br/>Outlier Events]
|
||||||
|
|
||||||
|
C --> I[shorteventid_authchain<br/>Authorization Chains]
|
||||||
|
C --> J[shorteventid_shortstatehash<br/>Event → State Mapping]
|
||||||
|
|
||||||
|
G -.->|Shared Cache| H
|
||||||
|
|
||||||
|
style G fill:#e1f5fe
|
||||||
|
style H fill:#e1f5fe
|
||||||
|
style A fill:#fff3e0
|
||||||
|
style C fill:#f3e5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Room State Management System
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Room State Key] --> B[statekey_shortstatekey]
|
||||||
|
B --> C[Short State Key<br/>8 bytes]
|
||||||
|
C --> D[shortstatekey_statekey]
|
||||||
|
D --> A
|
||||||
|
|
||||||
|
E[Full State Hash] --> F[statehash_shortstatehash]
|
||||||
|
F --> G[Short State Hash<br/>8 bytes]
|
||||||
|
|
||||||
|
G --> H[shortstatehash_statediff<br/>State Differences]
|
||||||
|
G --> I[roomid_shortstatehash<br/>Current Room State]
|
||||||
|
G --> J[roomsynctoken_shortstatehash<br/>Sync Token Mapping]
|
||||||
|
|
||||||
|
K[Room ID] --> I
|
||||||
|
L[Sync Token] --> J
|
||||||
|
|
||||||
|
style G fill:#e8f5e8
|
||||||
|
style I fill:#fff3e0
|
||||||
|
style J fill:#f0f4ff
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. User Authentication and Identity Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[User ID] --> B[userid_password<br/>Password Hashes]
|
||||||
|
|
||||||
|
A --> C[userid_displayname]
|
||||||
|
A --> D[userid_avatarurl]
|
||||||
|
D --> E[userid_blurhash]
|
||||||
|
A --> F[useridprofilekey_value<br/>Custom Profile]
|
||||||
|
|
||||||
|
G[Access Token] --> H[token_userdeviceid]
|
||||||
|
H --> I[User + Device ID]
|
||||||
|
I --> J[userdeviceid_token]
|
||||||
|
J --> G
|
||||||
|
|
||||||
|
I --> K[userdeviceid_metadata<br/>Device Info]
|
||||||
|
I --> L[userdevicesessionid_uiaainfo<br/>Auth Sessions]
|
||||||
|
I --> M[userdevicetxnid_response<br/>Transaction Cache]
|
||||||
|
|
||||||
|
N[OpenID Token] --> O[openidtoken_expiresatuserid]
|
||||||
|
P[Login Token] --> Q[logintoken_expiresatuserid]
|
||||||
|
|
||||||
|
style H fill:#e1f5fe
|
||||||
|
style J fill:#e1f5fe
|
||||||
|
style B fill:#ffebee
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Room Membership Bidirectional System
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Room ID + User ID] --> B[roomuserid_joined<br/>Room → User View]
|
||||||
|
C[User ID + Room ID] --> D[userroomid_joined<br/>User → Room View]
|
||||||
|
|
||||||
|
B -.->|Bidirectional| D
|
||||||
|
|
||||||
|
A --> E[roomuserid_invitecount]
|
||||||
|
C --> F[userroomid_invitestate]
|
||||||
|
E -.->|Related| F
|
||||||
|
|
||||||
|
A --> G[roomuserid_leftcount]
|
||||||
|
C --> H[userroomid_leftstate]
|
||||||
|
G -.->|Related| H
|
||||||
|
|
||||||
|
A --> I[roomuserid_knockedcount]
|
||||||
|
C --> J[userroomid_knockedstate]
|
||||||
|
I -.->|Related| J
|
||||||
|
|
||||||
|
K[Room ID] --> L[roomid_joinedcount<br/>Total Joined]
|
||||||
|
K --> M[roomid_invitedcount<br/>Total Invited]
|
||||||
|
|
||||||
|
N[Historical] --> O[roomuseroncejoinedids<br/>Ever Joined Tracking]
|
||||||
|
|
||||||
|
style B fill:#e8f5e8
|
||||||
|
style D fill:#e8f5e8
|
||||||
|
style L fill:#fff3e0
|
||||||
|
style M fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Cryptography and Key Management Chain
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[User ID] --> B[userid_devicelistversion<br/>Device List Changes]
|
||||||
|
|
||||||
|
A --> C[userid_masterkeyid<br/>Master Signing Key]
|
||||||
|
A --> D[userid_selfsigningkeyid<br/>Self Signing Key]
|
||||||
|
A --> E[userid_usersigningkeyid<br/>User Signing Key]
|
||||||
|
|
||||||
|
F[Key ID] --> G[keyid_key<br/>Actual Keys]
|
||||||
|
|
||||||
|
C --> G
|
||||||
|
D --> G
|
||||||
|
E --> G
|
||||||
|
|
||||||
|
H[Key Change ID] --> I[keychangeid_userid<br/>Change Notifications]
|
||||||
|
|
||||||
|
J[One-Time Key ID] --> K[onetimekeyid_onetimekeys<br/>OTK Storage]
|
||||||
|
A --> L[userid_lastonetimekeyupdate<br/>Last OTK Update]
|
||||||
|
|
||||||
|
M[Backup ID] --> N[backupid_algorithm<br/>Backup Algorithm]
|
||||||
|
M --> O[backupid_etag<br/>Backup Versioning]
|
||||||
|
P[Backup Key ID] --> Q[backupkeyid_backup<br/>Backed Up Keys]
|
||||||
|
|
||||||
|
style G fill:#e1f5fe
|
||||||
|
style I fill:#fff3e0
|
||||||
|
style K fill:#f3e5f5
|
||||||
|
style Q fill:#e8f5e8
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Federation and Server Communication
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Server Name] --> B[servername_destination<br/>Cached Destinations]
|
||||||
|
A --> C[servername_override<br/>Cached Overrides]
|
||||||
|
A --> D[server_signingkeys<br/>Federation Keys]
|
||||||
|
A --> E[servername_educount<br/>EDU Counters]
|
||||||
|
|
||||||
|
F[Server + Event] --> G[servernameevent_data<br/>Server Events]
|
||||||
|
H[Server Current] --> I[servercurrentevent_data<br/>Current State]
|
||||||
|
|
||||||
|
J[Room ID] --> K[roomserverids<br/>Room → Servers]
|
||||||
|
L[Server Name] --> M[serverroomids<br/>Server → Rooms]
|
||||||
|
|
||||||
|
K -.->|Bidirectional| M
|
||||||
|
|
||||||
|
N[Room ID] --> O[roomid_inviteviaservers<br/>Invitation Routing]
|
||||||
|
|
||||||
|
style B fill:#e1f5fe
|
||||||
|
style C fill:#e1f5fe
|
||||||
|
style K fill:#e8f5e8
|
||||||
|
style M fill:#e8f5e8
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Push Notifications and Read Tracking
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Sender Key] --> B[senderkey_pusher<br/>Push Endpoints]
|
||||||
|
C[Push Key] --> D[pushkey_deviceid<br/>Device Mapping]
|
||||||
|
|
||||||
|
B -.->|Related| D
|
||||||
|
|
||||||
|
E[Read Receipt ID] --> F[readreceiptid_readreceipt<br/>Public Receipts]
|
||||||
|
|
||||||
|
G[Room + User] --> H[roomuserid_privateread<br/>Private Read Markers]
|
||||||
|
G --> I[roomuserid_lastprivatereadupdate<br/>Update Timestamps]
|
||||||
|
|
||||||
|
J[User + Room] --> K[userroomid_highlightcount<br/>Mention Count]
|
||||||
|
J --> L[userroomid_notificationcount<br/>Notification Count]
|
||||||
|
|
||||||
|
style F fill:#e8f5e8
|
||||||
|
style H fill:#f3e5f5
|
||||||
|
style K fill:#fff3e0
|
||||||
|
style L fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Media and Content Management
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Media ID] --> B[mediaid_file<br/>File Metadata]
|
||||||
|
A --> C[mediaid_user<br/>Uploader Tracking]
|
||||||
|
|
||||||
|
B -.->|Related| C
|
||||||
|
|
||||||
|
D[URL] --> E[url_previews<br/>Preview Cache]
|
||||||
|
|
||||||
|
F[User ID] --> G[userfilterid_filter<br/>Sync Filters]
|
||||||
|
H[Lazy Load] --> I[lazyloadedids<br/>Member Event Tracking]
|
||||||
|
|
||||||
|
style B fill:#e1f5fe
|
||||||
|
style C fill:#e1f5fe
|
||||||
|
style E fill:#f0f4ff
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Account Data and Presence System
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Room + User + Type] --> B[roomusertype_roomuserdataid<br/>Account Data Index]
|
||||||
|
B --> C[Room User Data ID]
|
||||||
|
C --> D[roomuserdataid_accountdata<br/>Actual Account Data]
|
||||||
|
|
||||||
|
E[User ID] --> F[userid_presenceid<br/>Presence Mapping]
|
||||||
|
F --> G[Presence ID]
|
||||||
|
G --> H[presenceid_presence<br/>Presence Data]
|
||||||
|
|
||||||
|
I[To-Device ID] --> J[todeviceid_events<br/>Device Messages]
|
||||||
|
|
||||||
|
style D fill:#e8f5e8
|
||||||
|
style H fill:#f3e5f5
|
||||||
|
style J fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Global Configuration and Access Control
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Global Config] --> B[global<br/>Server Settings]
|
||||||
|
|
||||||
|
C[Room Categories] --> D[publicroomids<br/>Public Rooms]
|
||||||
|
C --> E[bannedroomids<br/>Banned Rooms]
|
||||||
|
C --> F[disabledroomids<br/>Disabled Rooms]
|
||||||
|
|
||||||
|
G[App Service ID] --> H[id_appserviceregistrations<br/>Application Services]
|
||||||
|
|
||||||
|
I[Token Management] --> J[tokenids<br/>Token Allocation]
|
||||||
|
|
||||||
|
K[Relations] --> L[tofrom_relation<br/>Event Relations]
|
||||||
|
K --> M[threadid_userids<br/>Thread Participants]
|
||||||
|
K --> N[referencedevents<br/>Referenced Events]
|
||||||
|
K --> O[softfailedeventids<br/>Failed Events]
|
||||||
|
|
||||||
|
style B fill:#e1f5fe
|
||||||
|
style D fill:#e8f5e8
|
||||||
|
style E fill:#ffebee
|
||||||
|
style F fill:#ffebee
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Complete System Overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "Identity Management"
|
||||||
|
UI[User Identity]
|
||||||
|
UA[User Auth]
|
||||||
|
UD[User Devices]
|
||||||
|
UP[User Profile]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Event Storage"
|
||||||
|
ES[Event Storage]
|
||||||
|
EID[Event ID Mapping]
|
||||||
|
EO[Outlier Events]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Room Management"
|
||||||
|
RS[Room State]
|
||||||
|
RM[Room Membership]
|
||||||
|
RMeta[Room Metadata]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Cryptography"
|
||||||
|
DK[Device Keys]
|
||||||
|
CS[Cross Signing]
|
||||||
|
KB[Key Backups]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Federation"
|
||||||
|
FS[Federation Servers]
|
||||||
|
FK[Federation Keys]
|
||||||
|
FE[Federation Events]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Communication"
|
||||||
|
PUSH[Push Notifications]
|
||||||
|
RT[Read Tracking]
|
||||||
|
DM[Device Messages]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Content"
|
||||||
|
MC[Media Content]
|
||||||
|
UP2[URL Previews]
|
||||||
|
AD[Account Data]
|
||||||
|
end
|
||||||
|
|
||||||
|
UI --> UA
|
||||||
|
UA --> UD
|
||||||
|
UI --> UP
|
||||||
|
|
||||||
|
ES --> EID
|
||||||
|
ES --> EO
|
||||||
|
EID --> RS
|
||||||
|
|
||||||
|
RS --> RM
|
||||||
|
RM --> RMeta
|
||||||
|
|
||||||
|
UD --> DK
|
||||||
|
DK --> CS
|
||||||
|
CS --> KB
|
||||||
|
|
||||||
|
RM --> FS
|
||||||
|
FS --> FK
|
||||||
|
FK --> FE
|
||||||
|
|
||||||
|
UD --> PUSH
|
||||||
|
RM --> RT
|
||||||
|
UD --> DM
|
||||||
|
|
||||||
|
UI --> MC
|
||||||
|
MC --> UP2
|
||||||
|
UI --> AD
|
||||||
|
|
||||||
|
style UI fill:#e8f5e8
|
||||||
|
style ES fill:#e1f5fe
|
||||||
|
style RS fill:#f3e5f5
|
||||||
|
style DK fill:#fff3e0
|
||||||
|
style FS fill:#f0f4ff
|
||||||
|
```
|
||||||
|
|
||||||
|
## Diagram Legend
|
||||||
|
|
||||||
|
- **Blue boxes** (`#e1f5fe`): Core storage tables
|
||||||
|
- **Green boxes** (`#e8f5e8`): Membership and relationship tables
|
||||||
|
- **Purple boxes** (`#f3e5f5`): ID mapping and compression tables
|
||||||
|
- **Orange boxes** (`#fff3e0`): Count and metadata tables
|
||||||
|
- **Light blue boxes** (`#f0f4ff`): Sync and federation tables
|
||||||
|
- **Red boxes** (`#ffebee`): Access control and security tables
|
||||||
|
- **Solid arrows**: Direct relationships
|
||||||
|
- **Dotted arrows**: Bidirectional or related tables
|
||||||
|
- **Shared Cache notation**: Tables that share memory pools
|
||||||
|
|
||||||
|
These diagrams show how Continuwuity's 89 database tables interconnect to provide a complete Matrix homeserver implementation with optimized storage patterns and efficient relationship management.
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
# Continuwuity Database Column Relationships
|
||||||
|
|
||||||
|
This document analyzes how the 89 database columns in Continuwuity relate to each other, showing the data flow and dependencies between tables.
|
||||||
|
|
||||||
|
## Core Identity Mapping System
|
||||||
|
|
||||||
|
### Event ID Management
|
||||||
|
|
||||||
|
The system uses a sophisticated event ID mapping system to optimize storage:
|
||||||
|
|
||||||
|
```
|
||||||
|
eventid_shorteventid ←→ shorteventid_eventid
|
||||||
|
↓ ↓
|
||||||
|
eventid_pduid shorteventid_authchain
|
||||||
|
↓ ↓
|
||||||
|
pduid_pdu shorteventid_shortstatehash
|
||||||
|
↓
|
||||||
|
eventid_outlierpdu
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- `eventid_shorteventid` + `shorteventid_eventid`: Bidirectional mapping between full Matrix event IDs (48 bytes) and compact short IDs (8 bytes)
|
||||||
|
- `eventid_pduid`: Maps event IDs to PDU IDs (16-byte internal identifiers)
|
||||||
|
- `pduid_pdu`: Main event storage using PDU IDs as keys
|
||||||
|
- `eventid_outlierpdu`: Stores events not yet part of the timeline (outliers)
|
||||||
|
- `shorteventid_authchain`: Authorization chains using short event IDs
|
||||||
|
- `shorteventid_shortstatehash`: Links events to room state using short IDs
|
||||||
|
|
||||||
|
### Room ID Management
|
||||||
|
|
||||||
|
Similar optimization for room identifiers:
|
||||||
|
|
||||||
|
```
|
||||||
|
roomid_shortroomid
|
||||||
|
↓
|
||||||
|
(used in keys for room-related tables)
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
Complex state tracking with compression:
|
||||||
|
|
||||||
|
```
|
||||||
|
statekey_shortstatekey ←→ shortstatekey_statekey
|
||||||
|
↓
|
||||||
|
statehash_shortstatehash
|
||||||
|
↓
|
||||||
|
shortstatehash_statediff
|
||||||
|
↓
|
||||||
|
roomid_shortstatehash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- `statekey_shortstatekey` + `shortstatekey_statekey`: Bidirectional mapping for state keys
|
||||||
|
- `statehash_shortstatehash`: Maps full state hashes to 8-byte compressed versions
|
||||||
|
- `shortstatehash_statediff`: Stores state differences between versions
|
||||||
|
- `roomid_shortstatehash`: Current state hash for each room
|
||||||
|
|
||||||
|
## User and Authentication Flow
|
||||||
|
|
||||||
|
### User Authentication Chain
|
||||||
|
|
||||||
|
```
|
||||||
|
userid_password → token_userdeviceid ←→ userdeviceid_token
|
||||||
|
↓
|
||||||
|
userdeviceid_metadata
|
||||||
|
↓
|
||||||
|
userdevicesessionid_uiaainfo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- `userid_password`: Stores user password hashes
|
||||||
|
- `token_userdeviceid` + `userdeviceid_token`: Bidirectional mapping between access tokens and devices
|
||||||
|
- `userdeviceid_metadata`: Device information (name, type, etc.)
|
||||||
|
- `userdevicesessionid_uiaainfo`: User-Interactive Authentication session data
|
||||||
|
|
||||||
|
### User Profile Data
|
||||||
|
|
||||||
|
```
|
||||||
|
userid_displayname
|
||||||
|
userid_avatarurl → userid_blurhash
|
||||||
|
useridprofilekey_value
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Profile data is stored separately per attribute
|
||||||
|
- `userid_blurhash` complements `userid_avatarurl` for progressive loading
|
||||||
|
|
||||||
|
### Token Management
|
||||||
|
|
||||||
|
```
|
||||||
|
openidtoken_expiresatuserid
|
||||||
|
logintoken_expiresatuserid
|
||||||
|
tokenids
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Separate token types have separate expiration tracking
|
||||||
|
- `tokenids` manages token ID allocation
|
||||||
|
|
||||||
|
## Room Membership System
|
||||||
|
|
||||||
|
### Membership State Tracking
|
||||||
|
|
||||||
|
```
|
||||||
|
roomuserid_joined ←→ userroomid_joined
|
||||||
|
roomuserid_invitecount ←→ userroomid_invitestate
|
||||||
|
roomuserid_leftcount ←→ userroomid_leftstate
|
||||||
|
roomuserid_knockedcount ←→ userroomid_knockedstate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Bidirectional indexes: room→user and user→room perspectives
|
||||||
|
- Count tables track membership transitions
|
||||||
|
- State tables store membership event data
|
||||||
|
|
||||||
|
### Room Counts and Metadata
|
||||||
|
|
||||||
|
```
|
||||||
|
roomid_joinedcount ← roomuserid_joined
|
||||||
|
roomid_invitedcount ← roomuserid_invitecount
|
||||||
|
roomuseroncejoinedids (historical tracking)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Count tables are derived from individual membership records
|
||||||
|
- Historical tracking for users who ever joined
|
||||||
|
|
||||||
|
### Federation Integration
|
||||||
|
|
||||||
|
```
|
||||||
|
roomserverids ←→ serverroomids
|
||||||
|
roomid_inviteviaservers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Bidirectional tracking of which servers participate in which rooms
|
||||||
|
- Via servers for invitation routing
|
||||||
|
|
||||||
|
## Cryptography and Security
|
||||||
|
|
||||||
|
### Device Key Management
|
||||||
|
|
||||||
|
```
|
||||||
|
userid_devicelistversion
|
||||||
|
↓
|
||||||
|
keyid_key ← userid_masterkeyid
|
||||||
|
↓ userid_selfsigningkeyid
|
||||||
|
keychangeid_userid ← userid_usersigningkeyid
|
||||||
|
↓
|
||||||
|
onetimekeyid_onetimekeys
|
||||||
|
↓
|
||||||
|
userid_lastonetimekeyupdate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Device list versions track changes requiring key updates
|
||||||
|
- Different key types stored separately with references from user records
|
||||||
|
- Key changes trigger notifications
|
||||||
|
- One-time keys managed with update timestamps
|
||||||
|
|
||||||
|
### Key Backup System
|
||||||
|
|
||||||
|
```
|
||||||
|
backupid_algorithm
|
||||||
|
backupid_etag → backupkeyid_backup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Backup metadata (algorithm, versioning) linked to actual backed-up keys
|
||||||
|
|
||||||
|
## Push Notifications and Read Tracking
|
||||||
|
|
||||||
|
### Push Infrastructure
|
||||||
|
|
||||||
|
```
|
||||||
|
senderkey_pusher ←→ pushkey_deviceid
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Bidirectional mapping between push keys and devices
|
||||||
|
|
||||||
|
### Read Receipt System
|
||||||
|
|
||||||
|
```
|
||||||
|
readreceiptid_readreceipt
|
||||||
|
roomuserid_privateread
|
||||||
|
roomuserid_lastprivatereadupdate
|
||||||
|
userroomid_highlightcount
|
||||||
|
userroomid_notificationcount
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Public read receipts vs private read markers
|
||||||
|
- Highlight/notification counts per user-room pair
|
||||||
|
- Update tracking for private reads
|
||||||
|
|
||||||
|
## Media and Content
|
||||||
|
|
||||||
|
### Media Storage
|
||||||
|
|
||||||
|
```
|
||||||
|
mediaid_file ←→ mediaid_user
|
||||||
|
url_previews
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- File metadata linked to uploader tracking
|
||||||
|
- URL previews cached separately
|
||||||
|
|
||||||
|
## Sync and Timeline
|
||||||
|
|
||||||
|
### Sync Token Management
|
||||||
|
|
||||||
|
```
|
||||||
|
roomsynctoken_shortstatehash
|
||||||
|
lazyloadedids
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Sync tokens map to room state for efficient delta computation
|
||||||
|
- Lazy loading tracking for member events
|
||||||
|
|
||||||
|
### Event Relations
|
||||||
|
|
||||||
|
```
|
||||||
|
tofrom_relation
|
||||||
|
threadid_userids
|
||||||
|
referencedevents
|
||||||
|
softfailedeventids
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Event relations track replies, edits, reactions
|
||||||
|
- Thread participant tracking
|
||||||
|
- Referenced events and soft failures
|
||||||
|
|
||||||
|
## Federation and Server Management
|
||||||
|
|
||||||
|
### Server Discovery and Communication
|
||||||
|
|
||||||
|
```
|
||||||
|
servername_destination (cached)
|
||||||
|
servername_override (cached)
|
||||||
|
server_signingkeys
|
||||||
|
servername_educount
|
||||||
|
servercurrentevent_data
|
||||||
|
servernameevent_data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Destination resolution with caching
|
||||||
|
- Server signing keys for federation
|
||||||
|
- EDU (Ephemeral Data Unit) counting
|
||||||
|
- Current and historical server events
|
||||||
|
|
||||||
|
## Account Data and Presence
|
||||||
|
|
||||||
|
### Account Data Storage
|
||||||
|
|
||||||
|
```
|
||||||
|
roomusertype_roomuserdataid → roomuserdataid_accountdata
|
||||||
|
userid_presenceid → presenceid_presence
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Account data indexed by room+user+type, pointing to actual data
|
||||||
|
- Presence data separated from user records with ID mapping
|
||||||
|
|
||||||
|
## Global Configuration
|
||||||
|
|
||||||
|
### Application Services
|
||||||
|
|
||||||
|
```
|
||||||
|
id_appserviceregistrations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Global Settings
|
||||||
|
|
||||||
|
```
|
||||||
|
global
|
||||||
|
publicroomids
|
||||||
|
bannedroomids
|
||||||
|
disabledroomids
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Global server configuration
|
||||||
|
- Room access control lists
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### Shared Cache Relationships
|
||||||
|
|
||||||
|
- `eventid_outlierpdu` and `pduid_pdu` share cache because they both store PDU data
|
||||||
|
- Related tables are grouped for memory efficiency
|
||||||
|
|
||||||
|
### Transaction Management
|
||||||
|
|
||||||
|
```
|
||||||
|
userdevicetxnid_response
|
||||||
|
todeviceid_events
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relationships:**
|
||||||
|
|
||||||
|
- Transaction ID response caching
|
||||||
|
- To-device message queuing
|
||||||
|
|
||||||
|
## Data Flow Examples
|
||||||
|
|
||||||
|
### Sending a Message
|
||||||
|
|
||||||
|
1. `pduid_pdu` ← stores the PDU
|
||||||
|
2. `eventid_pduid` ← maps event ID to PDU ID
|
||||||
|
3. `eventid_shorteventid` ← creates short ID mapping
|
||||||
|
4. `shorteventid_shortstatehash` ← links to room state
|
||||||
|
5. `userroomid_notificationcount` ← updates notification counts
|
||||||
|
6. `readreceiptid_readreceipt` ← processes read receipts
|
||||||
|
|
||||||
|
### User Login
|
||||||
|
|
||||||
|
1. `userid_password` ← validates credentials
|
||||||
|
2. `userdeviceid_token` ← creates device token
|
||||||
|
3. `token_userdeviceid` ← creates reverse mapping
|
||||||
|
4. `userdeviceid_metadata` ← stores device info
|
||||||
|
|
||||||
|
### Room Join
|
||||||
|
|
||||||
|
1. `roomuserid_joined` ← records membership
|
||||||
|
2. `userroomid_joined` ← creates reverse index
|
||||||
|
3. `roomid_joinedcount` ← updates room count
|
||||||
|
4. `roomuseroncejoinedids` ← historical tracking
|
||||||
|
5. `roomserverids` ← federation tracking
|
||||||
|
|
||||||
|
This relational structure allows Continuwuity to efficiently handle Matrix protocol operations while maintaining data consistency and enabling fast lookups from multiple perspectives.
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
# Continuwuity Database Schema Documentation
|
||||||
|
|
||||||
|
Continuwuity is a Matrix protocol implementation using RocksDB as its storage backend. The database is organized into column families (called "Maps" in the codebase), each serving specific purposes in the Matrix homeserver functionality.
|
||||||
|
|
||||||
|
| Table Name | Access Pattern | Key Size | Value Size | Description |
|
||||||
|
|------------|---------------|----------|------------|-------------|
|
||||||
|
| `alias_roomid` | RANDOM_SMALL | - | - | Maps room alias to room ID |
|
||||||
|
| `alias_userid` | RANDOM_SMALL | - | - | Maps room alias to user ID |
|
||||||
|
| `aliasid_alias` | RANDOM_SMALL | - | - | Maps alias ID to alias string |
|
||||||
|
| `backupid_algorithm` | RANDOM_SMALL | - | - | Key backup algorithms |
|
||||||
|
| `backupid_etag` | RANDOM_SMALL | - | - | Key backup ETags |
|
||||||
|
| `backupkeyid_backup` | RANDOM_SMALL | - | - | Backed up keys |
|
||||||
|
| `bannedroomids` | RANDOM_SMALL | - | - | Set of banned room IDs |
|
||||||
|
| `disabledroomids` | RANDOM_SMALL | - | - | Set of disabled room IDs |
|
||||||
|
| `eventid_outlierpdu` | RANDOM | 48 bytes | 1488 bytes | Outlier PDUs (shared cache with pduid_pdu) |
|
||||||
|
| `eventid_pduid` | RANDOM | 48 bytes | 16 bytes | Event ID to PDU ID mapping |
|
||||||
|
| `eventid_shorteventid` | RANDOM | 48 bytes | 8 bytes | Event ID to short event ID |
|
||||||
|
| `global` | RANDOM_SMALL | - | - | Global server configuration |
|
||||||
|
| `id_appserviceregistrations` | RANDOM_SMALL | - | - | Application service registrations |
|
||||||
|
| `keychangeid_userid` | RANDOM | - | - | Key change notifications |
|
||||||
|
| `keyid_key` | RANDOM_SMALL | - | - | Cryptographic keys |
|
||||||
|
| `lazyloadedids` | RANDOM_SMALL | - | - | Lazy-loaded member events |
|
||||||
|
| `mediaid_file` | RANDOM_SMALL | - | - | Media file metadata |
|
||||||
|
| `mediaid_user` | RANDOM_SMALL | - | - | Media uploader tracking |
|
||||||
|
| `onetimekeyid_onetimekeys` | RANDOM_SMALL | - | - | One-time keys |
|
||||||
|
| `pduid_pdu` | SEQUENTIAL | 16 bytes | 1520 bytes | Main PDU storage (shared cache with eventid_outlierpdu) |
|
||||||
|
| `publicroomids` | RANDOM_SMALL | - | - | Public room IDs |
|
||||||
|
| `pushkey_deviceid` | RANDOM_SMALL | - | - | Push key to device mapping |
|
||||||
|
| `presenceid_presence` | SEQUENTIAL_SMALL | - | - | User presence data |
|
||||||
|
| `readreceiptid_readreceipt` | RANDOM | - | - | Read receipts |
|
||||||
|
| `referencedevents` | RANDOM | - | - | Referenced events |
|
||||||
|
| `roomid_invitedcount` | RANDOM_SMALL | - | - | Room invited user count |
|
||||||
|
| `roomid_inviteviaservers` | RANDOM_SMALL | - | - | Room invite via servers |
|
||||||
|
| `roomid_joinedcount` | RANDOM_SMALL | - | - | Room joined user count |
|
||||||
|
| `roomid_pduleaves` | RANDOM_SMALL | - | - | PDU leaves per room |
|
||||||
|
| `roomid_shortroomid` | RANDOM_SMALL | - | 8 bytes | Room ID to short room ID |
|
||||||
|
| `roomid_shortstatehash` | RANDOM_SMALL | - | 8 bytes | Room ID to state hash |
|
||||||
|
| `roomserverids` | RANDOM_SMALL | - | - | Server IDs per room |
|
||||||
|
| `roomsynctoken_shortstatehash` | SEQUENTIAL | - | 8 bytes | Sync token to state hash (special compression) |
|
||||||
|
| `roomuserdataid_accountdata` | RANDOM_SMALL | - | - | Room account data |
|
||||||
|
| `roomuserid_invitecount` | RANDOM_SMALL | - | 8 bytes | Room-user invite count |
|
||||||
|
| `roomuserid_joined` | RANDOM_SMALL | - | - | Room-user joined status |
|
||||||
|
| `roomuserid_lastprivatereadupdate` | RANDOM_SMALL | - | - | Last private read update |
|
||||||
|
| `roomuserid_leftcount` | RANDOM | - | 8 bytes | Room-user leave count |
|
||||||
|
| `roomuserid_knockedcount` | RANDOM_SMALL | - | 8 bytes | Room-user knock count |
|
||||||
|
| `roomuserid_privateread` | RANDOM_SMALL | - | - | Private read markers |
|
||||||
|
| `roomuseroncejoinedids` | RANDOM | - | - | Users who ever joined |
|
||||||
|
| `roomusertype_roomuserdataid` | RANDOM_SMALL | - | - | Account data type mapping |
|
||||||
|
| `senderkey_pusher` | RANDOM_SMALL | - | - | Push notification senders |
|
||||||
|
| `server_signingkeys` | RANDOM | - | - | Server signing keys |
|
||||||
|
| `servercurrentevent_data` | RANDOM_SMALL | - | - | Current server events |
|
||||||
|
| `servername_destination` | RANDOM_SMALL_CACHE | - | - | Server destinations (cached) |
|
||||||
|
| `servername_educount` | RANDOM_SMALL | - | - | EDU counters |
|
||||||
|
| `servername_override` | RANDOM_SMALL_CACHE | - | - | Server name overrides (cached) |
|
||||||
|
| `servernameevent_data` | RANDOM | - | 128 bytes | Server event data |
|
||||||
|
| `serverroomids` | RANDOM_SMALL | - | - | Rooms per server |
|
||||||
|
| `shorteventid_authchain` | SEQUENTIAL | 8 bytes | - | Event authorization chains |
|
||||||
|
| `shorteventid_eventid` | SEQUENTIAL_SMALL | 8 bytes | 48 bytes | Short event ID to event ID |
|
||||||
|
| `shorteventid_shortstatehash` | SEQUENTIAL | 8 bytes | 8 bytes | Event to state hash mapping |
|
||||||
|
| `shortstatehash_statediff` | SEQUENTIAL_SMALL | 8 bytes | - | State differences |
|
||||||
|
| `shortstatekey_statekey` | RANDOM_SMALL | 8 bytes | 1016 bytes | Short state key to state key |
|
||||||
|
| `softfailedeventids` | RANDOM_SMALL | 48 bytes | - | Soft-failed events |
|
||||||
|
| `statehash_shortstatehash` | RANDOM | - | 8 bytes | State hash to short hash |
|
||||||
|
| `statekey_shortstatekey` | RANDOM | 1016 bytes | 8 bytes | State key to short key |
|
||||||
|
| `threadid_userids` | SEQUENTIAL_SMALL | - | - | Thread participants |
|
||||||
|
| `todeviceid_events` | RANDOM | - | - | To-device messages |
|
||||||
|
| `tofrom_relation` | RANDOM_SMALL | 8 bytes | 8 bytes | Event relations |
|
||||||
|
| `token_userdeviceid` | RANDOM_SMALL | - | - | Token to device mapping |
|
||||||
|
| `tokenids` | RANDOM | - | - | Token ID management |
|
||||||
|
| `url_previews` | RANDOM | - | - | URL preview cache |
|
||||||
|
| `userdeviceid_metadata` | RANDOM_SMALL | - | - | Device metadata |
|
||||||
|
| `userdeviceid_token` | RANDOM_SMALL | - | - | Device tokens |
|
||||||
|
| `userdevicesessionid_uiaainfo` | RANDOM_SMALL | - | - | UIAA session info |
|
||||||
|
| `userdevicetxnid_response` | RANDOM_SMALL | - | - | Transaction responses |
|
||||||
|
| `userfilterid_filter` | RANDOM_SMALL | - | - | User sync filters |
|
||||||
|
| `userid_avatarurl` | RANDOM_SMALL | - | - | User avatar URLs |
|
||||||
|
| `userid_blurhash` | RANDOM_SMALL | - | - | Avatar blurhashes |
|
||||||
|
| `userid_devicelistversion` | RANDOM_SMALL | - | - | Device list versions |
|
||||||
|
| `userid_displayname` | RANDOM_SMALL | - | - | User display names |
|
||||||
|
| `userid_lastonetimekeyupdate` | RANDOM_SMALL | - | - | Last OTK update time |
|
||||||
|
| `userid_masterkeyid` | RANDOM_SMALL | - | - | Master signing keys |
|
||||||
|
| `userid_password` | RANDOM | - | - | Password hashes |
|
||||||
|
| `userid_presenceid` | RANDOM_SMALL | - | - | User presence mapping |
|
||||||
|
| `userid_selfsigningkeyid` | RANDOM_SMALL | - | - | Self-signing keys |
|
||||||
|
| `userid_usersigningkeyid` | RANDOM_SMALL | - | - | User-signing keys |
|
||||||
|
| `useridprofilekey_value` | RANDOM_SMALL | - | - | Custom profile fields |
|
||||||
|
| `openidtoken_expiresatuserid` | RANDOM_SMALL | - | - | OpenID tokens |
|
||||||
|
| `logintoken_expiresatuserid` | RANDOM_SMALL | - | - | Login tokens |
|
||||||
|
| `userroomid_highlightcount` | RANDOM | - | - | Highlight counts |
|
||||||
|
| `userroomid_invitestate` | RANDOM_SMALL | - | - | User invite states |
|
||||||
|
| `userroomid_joined` | RANDOM | - | - | User joined rooms |
|
||||||
|
| `userroomid_leftstate` | RANDOM | - | - | User leave states |
|
||||||
|
| `userroomid_knockedstate` | RANDOM_SMALL | - | - | User knock states |
|
||||||
|
| `userroomid_notificationcount` | RANDOM | - | - | Notification counts |
|
||||||
|
|
||||||
|
## Access Pattern Definitions
|
||||||
|
|
||||||
|
### RANDOM
|
||||||
|
|
||||||
|
- Large datasets with random updates across keyspace
|
||||||
|
- Compaction priority: OldestSmallestSeqFirst
|
||||||
|
- Write buffer: 32MB
|
||||||
|
- Cache shards: 128
|
||||||
|
- Compression: Zstd level -3
|
||||||
|
- Bottommost compression: level 2
|
||||||
|
|
||||||
|
### SEQUENTIAL
|
||||||
|
|
||||||
|
- Large datasets with append-heavy updates
|
||||||
|
- Compaction priority: OldestLargestSeqFirst
|
||||||
|
- Write buffer: 64MB
|
||||||
|
- Level size: 32MB
|
||||||
|
- File size: 2MB
|
||||||
|
- Compression: Zstd level -2
|
||||||
|
|
||||||
|
### RANDOM_SMALL
|
||||||
|
|
||||||
|
- Small datasets with random updates
|
||||||
|
- Compaction style: Universal
|
||||||
|
- Write buffer: 16MB
|
||||||
|
- Level size: 512KB
|
||||||
|
- File size: 128KB
|
||||||
|
- Block size: 512 bytes
|
||||||
|
- Compression: Zstd level -4
|
||||||
|
|
||||||
|
### SEQUENTIAL_SMALL
|
||||||
|
|
||||||
|
- Small datasets with sequential updates
|
||||||
|
- Compaction style: Universal
|
||||||
|
- Write buffer: 16MB
|
||||||
|
- Level size: 1MB
|
||||||
|
- File size: 512KB
|
||||||
|
- Compression: Zstd level -4
|
||||||
|
|
||||||
|
### RANDOM_SMALL_CACHE
|
||||||
|
|
||||||
|
- Small persistent caches with TTL
|
||||||
|
- Compaction style: FIFO
|
||||||
|
- Size limit: 64MB
|
||||||
|
- TTL: 14 days
|
||||||
|
- Unique cache allocation
|
||||||
|
|
||||||
|
## Special Configurations
|
||||||
|
|
||||||
|
### Shared Cache Tables
|
||||||
|
|
||||||
|
- `eventid_outlierpdu` and `pduid_pdu` share cache pool
|
||||||
|
- Optimizes memory usage for related event data
|
||||||
|
|
||||||
|
### High-Performance Tables
|
||||||
|
|
||||||
|
- `roomsynctoken_shortstatehash`: Special compression settings for sync performance
|
||||||
|
- `pduid_pdu`: Large block size (2KB) for efficient event storage
|
||||||
|
- `eventid_outlierpdu`: Optimized for outlier PDU handling
|
||||||
|
|
||||||
|
### Cache-Only Tables
|
||||||
|
|
||||||
|
- `servername_destination`: FIFO cache for server resolution
|
||||||
|
- `servername_override`: FIFO cache for server overrides
|
||||||
|
|
||||||
|
## Data Types and Sizes
|
||||||
|
|
||||||
|
### Event IDs
|
||||||
|
|
||||||
|
- Full event IDs: 48 bytes (Matrix event ID format)
|
||||||
|
- Short event IDs: 8 bytes (internal optimization)
|
||||||
|
|
||||||
|
### Room IDs
|
||||||
|
|
||||||
|
- Full room IDs: Variable length Matrix room ID
|
||||||
|
- Short room IDs: 8 bytes (internal optimization)
|
||||||
|
|
||||||
|
### PDU Data
|
||||||
|
|
||||||
|
- PDU ID: 16 bytes
|
||||||
|
- PDU content: ~1520 bytes average
|
||||||
|
- Outlier PDUs: ~1488 bytes average
|
||||||
|
|
||||||
|
### State Data
|
||||||
|
|
||||||
|
- State keys: Up to 1016 bytes
|
||||||
|
- Short state keys: 8 bytes
|
||||||
|
- State hashes: 8 bytes (shortened)
|
||||||
|
|
||||||
|
This technical reference shows how Continuwuity optimizes storage for different types of Matrix data, using appropriate RocksDB configurations for each access pattern.
|
||||||
|
|
||||||
|
## Database Architecture
|
||||||
|
|
||||||
|
## Column Families (Maps)
|
||||||
|
|
||||||
|
### Room Management
|
||||||
|
|
||||||
|
#### Room Aliases
|
||||||
|
|
||||||
|
- **`alias_roomid`**: Maps room alias to room ID
|
||||||
|
- **`alias_userid`**: Maps room alias to user ID (for alias management)
|
||||||
|
- **`aliasid_alias`**: Maps alias ID to actual alias string
|
||||||
|
|
||||||
|
#### Room Metadata
|
||||||
|
|
||||||
|
- **`roomid_shortroomid`**: Maps room ID to short room ID (8-byte identifier)
|
||||||
|
- **`roomid_shortstatehash`**: Maps room ID to current state hash
|
||||||
|
- **`roomid_pduleaves`**: Tracks PDU leaves for each room
|
||||||
|
- **`roomid_invitedcount`**: Count of invited users per room
|
||||||
|
- **`roomid_joinedcount`**: Count of joined users per room
|
||||||
|
- **`roomid_inviteviaservers`**: Via servers for room invites
|
||||||
|
- **`publicroomids`**: Set of public room IDs
|
||||||
|
- **`bannedroomids`**: Set of banned room IDs
|
||||||
|
- **`disabledroomids`**: Set of disabled room IDs
|
||||||
|
|
||||||
|
#### Room State
|
||||||
|
|
||||||
|
- **`shortstatehash_statediff`**: State differences between state hashes
|
||||||
|
- **`statehash_shortstatehash`**: Maps full state hash to short state hash (8-byte)
|
||||||
|
- **`statekey_shortstatekey`**: Maps state key to short state key (8-byte)
|
||||||
|
- **`shortstatekey_statekey`**: Reverse mapping from short state key to full state key
|
||||||
|
- **`roomsynctoken_shortstatehash`**: Maps room sync tokens to state hashes
|
||||||
|
|
||||||
|
### Events and Timeline
|
||||||
|
|
||||||
|
#### Event Storage
|
||||||
|
|
||||||
|
- **`eventid_pduid`**: Maps event ID to PDU ID (16-byte identifier)
|
||||||
|
- **`eventid_shorteventid`**: Maps event ID to short event ID (8-byte)
|
||||||
|
- **`eventid_outlierpdu`**: Stores outlier PDUs (events not yet in timeline)
|
||||||
|
- **`pduid_pdu`**: Main PDU storage (PDU ID to PDU data)
|
||||||
|
- **`shorteventid_eventid`**: Reverse mapping from short event ID to full event ID
|
||||||
|
- **`shorteventid_authchain`**: Authorization chains for events
|
||||||
|
- **`shorteventid_shortstatehash`**: Maps events to their state hashes
|
||||||
|
|
||||||
|
#### Event Relationships
|
||||||
|
|
||||||
|
- **`tofrom_relation`**: Event relations (replies, edits, reactions)
|
||||||
|
- **`threadid_userids`**: Thread participants tracking
|
||||||
|
- **`referencedevents`**: Referenced events tracking
|
||||||
|
- **`softfailedeventids`**: Events that soft-failed state resolution
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
|
||||||
|
#### User Identity
|
||||||
|
|
||||||
|
- **`userid_displayname`**: User display names
|
||||||
|
- **`userid_avatarurl`**: User avatar URLs
|
||||||
|
- **`userid_blurhash`**: Avatar blurhash values
|
||||||
|
- **`userid_password`**: Password hashes
|
||||||
|
- **`useridprofilekey_value`**: Custom profile fields
|
||||||
|
|
||||||
|
#### User Devices and Sessions
|
||||||
|
|
||||||
|
- **`userdeviceid_metadata`**: Device metadata (name, type, etc.)
|
||||||
|
- **`userdeviceid_token`**: Device access tokens
|
||||||
|
- **`token_userdeviceid`**: Reverse token to device mapping
|
||||||
|
- **`userdevicesessionid_uiaainfo`**: User-Interactive Auth session data
|
||||||
|
- **`userdevicetxnid_response`**: Transaction ID to response caching
|
||||||
|
|
||||||
|
#### User Preferences
|
||||||
|
|
||||||
|
- **`userfilterid_filter`**: Sync filter definitions
|
||||||
|
- **`lazyloadedids`**: Lazy-loaded member event tracking
|
||||||
|
|
||||||
|
### Cryptography and Security
|
||||||
|
|
||||||
|
#### Device Keys
|
||||||
|
|
||||||
|
- **`keyid_key`**: Cryptographic keys storage
|
||||||
|
- **`userid_devicelistversion`**: Device list versions for users
|
||||||
|
- **`userid_lastonetimekeyupdate`**: Last one-time key update timestamps
|
||||||
|
- **`onetimekeyid_onetimekeys`**: One-time keys storage
|
||||||
|
|
||||||
|
#### Cross-Signing
|
||||||
|
|
||||||
|
- **`userid_masterkeyid`**: Master signing keys
|
||||||
|
- **`userid_selfsigningkeyid`**: Self-signing keys
|
||||||
|
- **`userid_usersigningkeyid`**: User-signing keys
|
||||||
|
- **`keychangeid_userid`**: Key change notifications
|
||||||
|
|
||||||
|
#### Key Backups
|
||||||
|
|
||||||
|
- **`backupid_algorithm`**: Backup algorithm information
|
||||||
|
- **`backupid_etag`**: Backup ETags for versioning
|
||||||
|
- **`backupkeyid_backup`**: Backed up keys
|
||||||
|
|
||||||
|
### Room Membership
|
||||||
|
|
||||||
|
#### Membership States
|
||||||
|
|
||||||
|
- **`roomuserid_joined`**: Current joined room members
|
||||||
|
- **`roomuserid_invitecount`**: Invite counts per room-user
|
||||||
|
- **`roomuserid_leftcount`**: Leave counts per room-user
|
||||||
|
- **`roomuserid_knockedcount`**: Knock counts per room-user
|
||||||
|
- **`roomuseroncejoinedids`**: Users who have ever joined rooms
|
||||||
|
|
||||||
|
#### Membership Events
|
||||||
|
|
||||||
|
- **`userroomid_joined`**: User's joined rooms
|
||||||
|
- **`userroomid_invitestate`**: Invite state events
|
||||||
|
- **`userroomid_leftstate`**: Leave state events
|
||||||
|
- **`userroomid_knockedstate`**: Knock state events
|
||||||
|
|
||||||
|
### Push Notifications and Read Receipts
|
||||||
|
|
||||||
|
#### Push Infrastructure
|
||||||
|
|
||||||
|
- **`senderkey_pusher`**: Push notification endpoints
|
||||||
|
- **`pushkey_deviceid`**: Push key to device mappings
|
||||||
|
|
||||||
|
#### Read Tracking
|
||||||
|
|
||||||
|
- **`readreceiptid_readreceipt`**: Read receipt storage
|
||||||
|
- **`roomuserid_privateread`**: Private read markers
|
||||||
|
- **`roomuserid_lastprivatereadupdate`**: Last private read updates
|
||||||
|
- **`userroomid_highlightcount`**: Highlight/mention counts
|
||||||
|
- **`userroomid_notificationcount`**: Notification counts per room
|
||||||
|
|
||||||
|
### Media and Content
|
||||||
|
|
||||||
|
#### Media Storage
|
||||||
|
|
||||||
|
- **`mediaid_file`**: Media file metadata
|
||||||
|
- **`mediaid_user`**: Media uploader tracking
|
||||||
|
- **`url_previews`**: URL preview cache
|
||||||
|
|
||||||
|
### Federation and Server-to-Server
|
||||||
|
|
||||||
|
#### Server Management
|
||||||
|
|
||||||
|
- **`server_signingkeys`**: Server signing keys
|
||||||
|
- **`servername_destination`**: Server destination resolution
|
||||||
|
- **`servername_educount`**: Ephemeral Data Unit counters
|
||||||
|
- **`servername_override`**: Server name overrides for federation
|
||||||
|
- **`servernameevent_data`**: Server event data
|
||||||
|
- **`roomserverids`**: Servers participating in rooms
|
||||||
|
- **`serverroomids`**: Rooms per server
|
||||||
|
- **`servercurrentevent_data`**: Current server event state
|
||||||
|
|
||||||
|
### Application Services
|
||||||
|
|
||||||
|
- **`id_appserviceregistrations`**: Application service registrations
|
||||||
|
|
||||||
|
### Account Data and Presence
|
||||||
|
|
||||||
|
#### Account Data
|
||||||
|
|
||||||
|
- **`roomuserdataid_accountdata`**: Room-specific account data
|
||||||
|
- **`roomusertype_roomuserdataid`**: Account data type mappings
|
||||||
|
|
||||||
|
#### Presence
|
||||||
|
|
||||||
|
- **`presenceid_presence`**: User presence information
|
||||||
|
- **`userid_presenceid`**: User to presence ID mapping
|
||||||
|
|
||||||
|
### To-Device Messages
|
||||||
|
|
||||||
|
- **`todeviceid_events`**: Direct device-to-device messages
|
||||||
|
|
||||||
|
### Authentication Tokens
|
||||||
|
|
||||||
|
- **`openidtoken_expiresatuserid`**: OpenID Connect tokens
|
||||||
|
- **`logintoken_expiresatuserid`**: Login tokens
|
||||||
|
- **`tokenids`**: Token ID management
|
||||||
|
|
||||||
|
### Global Configuration
|
||||||
|
|
||||||
|
- **`global`**: Global server settings and state
|
||||||
|
|
||||||
|
## Key Design Patterns
|
||||||
|
|
||||||
|
### Short Identifiers
|
||||||
|
|
||||||
|
Many tables use "short" versions of identifiers (8-byte integers) to reduce storage overhead:
|
||||||
|
|
||||||
|
- `shortroomid` for room IDs
|
||||||
|
- `shorteventid` for event IDs
|
||||||
|
- `shortstatekey` for state keys
|
||||||
|
- `shortstatehash` for state hashes
|
||||||
|
|
||||||
|
### Composite Keys
|
||||||
|
|
||||||
|
Key naming follows a pattern of `{primary}_{secondary}` to create efficient lookups:
|
||||||
|
|
||||||
|
- `roomuserid_*` for room-user relationships
|
||||||
|
- `userroomid_*` for user-room relationships
|
||||||
|
- `eventid_*` for event-related data
|
||||||
|
|
||||||
|
### Performance Optimizations
|
||||||
|
|
||||||
|
- **Cache sharing**: Related tables share cache pools (e.g., `eventid_outlierpdu` and `pduid_pdu`)
|
||||||
|
- **Access patterns**: Tables are optimized for their specific usage (RANDOM vs SEQUENTIAL)
|
||||||
|
- **Compression**: Different compression levels based on data characteristics
|
||||||
|
- **Block sizes**: Tuned based on expected key/value sizes
|
||||||
|
|
||||||
|
## Storage Efficiency
|
||||||
|
|
||||||
|
The schema is designed for efficiency in a Matrix homeserver context:
|
||||||
|
|
||||||
|
- Large event data uses sequential storage patterns
|
||||||
|
- Lookup tables use random access patterns
|
||||||
|
- Small metadata uses compressed storage
|
||||||
|
- Caching is strategically shared between related data
|
||||||
|
|
||||||
|
This design allows Continuwuity to efficiently handle the complex relationships and high-volume data typical in Matrix federation while maintaining good performance characteristics for both reads and writes.
|
||||||
|
|
||||||
|
## Column Relationships and Data Flow
|
||||||
|
|
||||||
|
### Core Event Storage Chain
|
||||||
|
|
||||||
|
The heart of the Matrix homeserver is event storage, which uses several interconnected tables:
|
||||||
|
|
||||||
|
- `eventid_shorteventid` ↔ `shorteventid_eventid`: Bidirectional mapping for event ID compression (48 bytes → 8 bytes)
|
||||||
|
- `eventid_pduid`: Maps Matrix event IDs to internal PDU IDs (16 bytes)
|
||||||
|
- `pduid_pdu`: Main event storage using PDU IDs as keys (shares cache with `eventid_outlierpdu`)
|
||||||
|
- `eventid_outlierpdu`: Stores events not yet integrated into the timeline
|
||||||
|
- `shorteventid_authchain`: Authorization chains using compressed event IDs
|
||||||
|
- `shorteventid_shortstatehash`: Links events to room state snapshots
|
||||||
|
|
||||||
|
### Room State Management
|
||||||
|
|
||||||
|
Room state is tracked through multiple interconnected tables:
|
||||||
|
|
||||||
|
- `statekey_shortstatekey` ↔ `shortstatekey_statekey`: Bidirectional state key compression
|
||||||
|
- `statehash_shortstatehash`: Compresses state hashes from full size to 8 bytes
|
||||||
|
- `shortstatehash_statediff`: Stores incremental state changes
|
||||||
|
- `roomid_shortstatehash`: Current state hash for each room
|
||||||
|
- `roomsynctoken_shortstatehash`: Maps sync tokens to state for efficient delta sync
|
||||||
|
|
||||||
|
### User Identity and Authentication
|
||||||
|
|
||||||
|
User management involves several related tables:
|
||||||
|
|
||||||
|
- `userid_password` → authentication base
|
||||||
|
- `token_userdeviceid` ↔ `userdeviceid_token`: Bidirectional token↔device mapping
|
||||||
|
- `userdeviceid_metadata`: Device information storage
|
||||||
|
- `userid_displayname`, `userid_avatarurl`, `userid_blurhash`: Profile data
|
||||||
|
- `openidtoken_expiresatuserid`, `logintoken_expiresatuserid`: Token management
|
||||||
|
|
||||||
|
### Room Membership Tracking
|
||||||
|
|
||||||
|
Membership uses bidirectional indexes for efficient queries:
|
||||||
|
|
||||||
|
- `roomuserid_joined` ↔ `userroomid_joined`: Current membership from both perspectives
|
||||||
|
- `roomuserid_invitecount` ↔ `userroomid_invitestate`: Invitation tracking
|
||||||
|
- `roomuserid_leftcount` ↔ `userroomid_leftstate`: Leave event tracking
|
||||||
|
- `roomid_joinedcount`, `roomid_invitedcount`: Aggregate room statistics
|
||||||
|
- `roomuseroncejoinedids`: Historical membership tracking
|
||||||
|
|
||||||
|
### Cryptography and Security Chain
|
||||||
|
|
||||||
|
End-to-end encryption involves coordinated key management:
|
||||||
|
|
||||||
|
- `userid_devicelistversion`: Tracks when device lists change
|
||||||
|
- `keyid_key`: Stores actual cryptographic keys
|
||||||
|
- `userid_masterkeyid`, `userid_selfsigningkeyid`, `userid_usersigningkeyid`: Cross-signing keys
|
||||||
|
- `onetimekeyid_onetimekeys` → `userid_lastonetimekeyupdate`: One-time key lifecycle
|
||||||
|
- `keychangeid_userid`: Key change notifications
|
||||||
|
- `backupid_algorithm`, `backupid_etag` → `backupkeyid_backup`: Key backup system
|
||||||
|
|
||||||
|
### Federation and Server Communication
|
||||||
|
|
||||||
|
Server-to-server communication requires coordinated tracking:
|
||||||
|
|
||||||
|
- `roomserverids` ↔ `serverroomids`: Bidirectional room↔server participation
|
||||||
|
- `servername_destination`, `servername_override`: Server resolution (both cached)
|
||||||
|
- `server_signingkeys`: Federation authentication
|
||||||
|
- `servername_educount`: Ephemeral data unit tracking
|
||||||
|
- `servernameevent_data`, `servercurrentevent_data`: Server event state
|
||||||
|
|
||||||
|
### Read Tracking and Notifications
|
||||||
|
|
||||||
|
Message read tracking involves multiple coordinated updates:
|
||||||
|
|
||||||
|
- `readreceiptid_readreceipt`: Public read receipts
|
||||||
|
- `roomuserid_privateread`, `roomuserid_lastprivatereadupdate`: Private read markers
|
||||||
|
- `userroomid_highlightcount`, `userroomid_notificationcount`: Per-room notification counts
|
||||||
|
- `senderkey_pusher` ↔ `pushkey_deviceid`: Push notification routing
|
||||||
|
|
||||||
|
### Account Data and Preferences
|
||||||
|
|
||||||
|
User preferences and account data use a two-level structure:
|
||||||
|
|
||||||
|
- `roomusertype_roomuserdataid` → `roomuserdataid_accountdata`: Type index points to actual data
|
||||||
|
- `userid_presenceid` → `presenceid_presence`: Presence data separation
|
||||||
|
- `userfilterid_filter`: Sync filter definitions
|
||||||
|
- `lazyloadedids`: Lazy loading state tracking
|
||||||
|
|
||||||
|
This interconnected design allows Continuwuity to efficiently handle Matrix protocol operations while maintaining data consistency and enabling fast lookups from multiple perspectives.
|
||||||
@@ -20,3 +20,4 @@
|
|||||||
- [Testing](development/testing.md)
|
- [Testing](development/testing.md)
|
||||||
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
|
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
|
||||||
- [Community (and Guidelines)](community.md)
|
- [Community (and Guidelines)](community.md)
|
||||||
|
- [Security](security.md)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{{#include ../SECURITY.md}}
|
||||||
Vendored
+4
@@ -4,6 +4,10 @@
|
|||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"message": "🎉 Continuwuity v0.5.0-rc.6 is now available! This release includes improved knock-restricted room handling, automatic support contact configuration, and a new HTML landing page. Check [the release notes for full details](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.0-rc.6) and upgrade instructions."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+6
-2
@@ -3,7 +3,7 @@
|
|||||||
"$id": "https://continwuity.org/schema/announcements.schema.json",
|
"$id": "https://continwuity.org/schema/announcements.schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"updates": {
|
"announcements": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -16,6 +16,10 @@
|
|||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mention_room": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to mention the room (@room) when posting this announcement"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -26,6 +30,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"updates"
|
"announcements"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -125,13 +125,13 @@ pub(super) enum DebugCommand {
|
|||||||
reset: bool,
|
reset: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// - Verify json signatures
|
/// - Sign JSON blob
|
||||||
///
|
///
|
||||||
/// This command needs a JSON blob provided in a Markdown code block below
|
/// This command needs a JSON blob provided in a Markdown code block below
|
||||||
/// the command.
|
/// the command.
|
||||||
SignJson,
|
SignJson,
|
||||||
|
|
||||||
/// - Verify json signatures
|
/// - Verify JSON signatures
|
||||||
///
|
///
|
||||||
/// This command needs a JSON blob provided in a Markdown code block below
|
/// This command needs a JSON blob provided in a Markdown code block below
|
||||||
/// the command.
|
/// the command.
|
||||||
|
|||||||
@@ -2162,6 +2162,109 @@ async fn knock_room_by_id_helper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For knock_restricted rooms, check if the user meets the restricted conditions
|
||||||
|
// If they do, attempt to join instead of knock
|
||||||
|
// This is not mentioned in the spec, but should be allowable (we're allowed to
|
||||||
|
// auto-join invites to knocked rooms)
|
||||||
|
let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await;
|
||||||
|
if let JoinRule::KnockRestricted(restricted) = &join_rule {
|
||||||
|
let restriction_rooms: Vec<_> = restricted
|
||||||
|
.allow
|
||||||
|
.iter()
|
||||||
|
.filter_map(|a| match a {
|
||||||
|
| AllowRule::RoomMembership(r) => Some(&r.room_id),
|
||||||
|
| _ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Check if the user is in any of the allowed rooms
|
||||||
|
let mut user_meets_restrictions = false;
|
||||||
|
for restriction_room_id in &restriction_rooms {
|
||||||
|
if services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(sender_user, restriction_room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
user_meets_restrictions = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user meets the restrictions, try joining instead
|
||||||
|
if user_meets_restrictions {
|
||||||
|
debug_info!(
|
||||||
|
"{sender_user} meets the restricted criteria in knock_restricted room \
|
||||||
|
{room_id}, attempting to join instead of knock"
|
||||||
|
);
|
||||||
|
// For this case, we need to drop the state lock and get a new one in
|
||||||
|
// join_room_by_id_helper We need to release the lock here and let
|
||||||
|
// join_room_by_id_helper acquire it again
|
||||||
|
drop(state_lock);
|
||||||
|
match join_room_by_id_helper(
|
||||||
|
services,
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
reason.clone(),
|
||||||
|
servers,
|
||||||
|
None,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
| Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())),
|
||||||
|
| Err(e) => {
|
||||||
|
debug_warn!(
|
||||||
|
"Failed to convert knock to join for {sender_user} in {room_id}: {e:?}"
|
||||||
|
);
|
||||||
|
// Get a new state lock for the remaining knock logic
|
||||||
|
let new_state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||||
|
|
||||||
|
let server_in_room = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), room_id)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let local_knock = server_in_room
|
||||||
|
|| servers.is_empty()
|
||||||
|
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
|
||||||
|
|
||||||
|
if local_knock {
|
||||||
|
knock_room_helper_local(
|
||||||
|
services,
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
reason,
|
||||||
|
servers,
|
||||||
|
new_state_lock,
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
knock_room_helper_remote(
|
||||||
|
services,
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
reason,
|
||||||
|
servers,
|
||||||
|
new_state_lock,
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(knock_room::v3::Response::new(room_id.to_owned()));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) {
|
||||||
|
debug_warn!(
|
||||||
|
"{sender_user} attempted to knock on room {room_id} but its join rule is \
|
||||||
|
{join_rule:?}, not knock or knock_restricted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let server_in_room = services
|
let server_in_room = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
@@ -2209,6 +2312,12 @@ async fn knock_room_helper_local(
|
|||||||
return Err!(Request(Forbidden("This room does not support knocking.")));
|
return Err!(Request(Forbidden("This room does not support knocking.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify that this room has a valid knock or knock_restricted join rule
|
||||||
|
let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await;
|
||||||
|
if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) {
|
||||||
|
return Err!(Request(Forbidden("This room's join rule does not allow knocking.")));
|
||||||
|
}
|
||||||
|
|
||||||
let content = RoomMemberEventContent {
|
let content = RoomMemberEventContent {
|
||||||
displayname: services.users.displayname(sender_user).await.ok(),
|
displayname: services.users.displayname(sender_user).await.ok(),
|
||||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use axum::{Json, extract::State, response::IntoResponse};
|
use axum::{Json, extract::State, response::IntoResponse};
|
||||||
use conduwuit::{Error, Result};
|
use conduwuit::{Error, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
discovery::{
|
discovery::{
|
||||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||||
@@ -17,7 +18,7 @@ pub(crate) async fn well_known_client(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
_body: Ruma<discover_homeserver::Request>,
|
_body: Ruma<discover_homeserver::Request>,
|
||||||
) -> Result<discover_homeserver::Response> {
|
) -> Result<discover_homeserver::Response> {
|
||||||
let client_url = match services.server.config.well_known.client.as_ref() {
|
let client_url = match services.config.well_known.client.as_ref() {
|
||||||
| Some(url) => url.to_string(),
|
| Some(url) => url.to_string(),
|
||||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||||
};
|
};
|
||||||
@@ -33,44 +34,63 @@ pub(crate) async fn well_known_client(
|
|||||||
/// # `GET /.well-known/matrix/support`
|
/// # `GET /.well-known/matrix/support`
|
||||||
///
|
///
|
||||||
/// Server support contact and support page of a homeserver's domain.
|
/// Server support contact and support page of a homeserver's domain.
|
||||||
|
/// Implements MSC1929 for server discovery.
|
||||||
|
/// If no configuration is set, uses admin users as contacts.
|
||||||
pub(crate) async fn well_known_support(
|
pub(crate) async fn well_known_support(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
_body: Ruma<discover_support::Request>,
|
_body: Ruma<discover_support::Request>,
|
||||||
) -> Result<discover_support::Response> {
|
) -> Result<discover_support::Response> {
|
||||||
let support_page = services
|
let support_page = services
|
||||||
.server
|
|
||||||
.config
|
.config
|
||||||
.well_known
|
.well_known
|
||||||
.support_page
|
.support_page
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(ToString::to_string);
|
.map(ToString::to_string);
|
||||||
|
|
||||||
let role = services.server.config.well_known.support_role.clone();
|
let email_address = services.config.well_known.support_email.clone();
|
||||||
|
let matrix_id = services.config.well_known.support_mxid.clone();
|
||||||
// support page or role must be either defined for this to be valid
|
|
||||||
if support_page.is_none() && role.is_none() {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
|
||||||
}
|
|
||||||
|
|
||||||
let email_address = services.server.config.well_known.support_email.clone();
|
|
||||||
let matrix_id = services.server.config.well_known.support_mxid.clone();
|
|
||||||
|
|
||||||
// if a role is specified, an email address or matrix id is required
|
|
||||||
if role.is_some() && (email_address.is_none() && matrix_id.is_none()) {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support defining multiple contacts in the config
|
// TODO: support defining multiple contacts in the config
|
||||||
let mut contacts: Vec<Contact> = vec![];
|
let mut contacts: Vec<Contact> = vec![];
|
||||||
|
|
||||||
if let Some(role) = role {
|
let role_value = services
|
||||||
let contact = Contact { role, email_address, matrix_id };
|
.config
|
||||||
|
.well_known
|
||||||
|
.support_role
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "m.role.admin".to_owned().into());
|
||||||
|
|
||||||
contacts.push(contact);
|
// Add configured contact if at least one contact method is specified
|
||||||
|
if email_address.is_some() || matrix_id.is_some() {
|
||||||
|
contacts.push(Contact {
|
||||||
|
role: role_value.clone(),
|
||||||
|
email_address: email_address.clone(),
|
||||||
|
matrix_id: matrix_id.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to add admin users as contacts if no contacts are configured
|
||||||
|
if contacts.is_empty() {
|
||||||
|
if let Ok(admin_room) = services.admin.get_admin_room().await {
|
||||||
|
let admin_users = services.rooms.state_cache.room_members(&admin_room);
|
||||||
|
let mut stream = admin_users;
|
||||||
|
|
||||||
|
while let Some(user_id) = stream.next().await {
|
||||||
|
// Skip server user
|
||||||
|
if *user_id == services.globals.server_user {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
contacts.push(Contact {
|
||||||
|
role: role_value.clone(),
|
||||||
|
email_address: None,
|
||||||
|
matrix_id: Some(user_id.to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// support page or role+contacts must be either defined for this to be valid
|
|
||||||
if contacts.is_empty() && support_page.is_none() {
|
if contacts.is_empty() && support_page.is_none() {
|
||||||
|
// No admin room, no configured contacts, and no support page
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +104,9 @@ pub(crate) async fn well_known_support(
|
|||||||
pub(crate) async fn syncv3_client_server_json(
|
pub(crate) async fn syncv3_client_server_json(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
let server_url = match services.server.config.well_known.client.as_ref() {
|
let server_url = match services.config.well_known.client.as_ref() {
|
||||||
| Some(url) => url.to_string(),
|
| Some(url) => url.to_string(),
|
||||||
| None => match services.server.config.well_known.server.as_ref() {
|
| None => match services.config.well_known.server.as_ref() {
|
||||||
| Some(url) => url.to_string(),
|
| Some(url) => url.to_string(),
|
||||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ build = "build.rs"
|
|||||||
[lib]
|
[lib]
|
||||||
path = "mod.rs"
|
path = "mod.rs"
|
||||||
crate-type = [
|
crate-type = [
|
||||||
"rlib",
|
"rlib",
|
||||||
# "dylib",
|
# "dylib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -28,7 +28,7 @@ crate-type = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = {version = "0.7", features = ["cargo-lock", "dependency-tree"]}
|
built = { version = "0.8", features = [] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -78,12 +78,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Rerun Triggers ---
|
// --- Rerun Triggers ---
|
||||||
// Rerun if the git HEAD changes
|
// TODO: The git rerun triggers seem to always run
|
||||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
// // Rerun if the git HEAD changes
|
||||||
// Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch)
|
// println!("cargo:rerun-if-changed=.git/HEAD");
|
||||||
if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"]) {
|
// // Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch)
|
||||||
println!("cargo:rerun-if-changed=.git/{ref_path}");
|
// if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"])
|
||||||
}
|
// { println!("cargo:rerun-if-changed=.git/{ref_path}");
|
||||||
|
// }
|
||||||
|
|
||||||
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH");
|
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH");
|
||||||
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH_SHORT");
|
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH_SHORT");
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ pub static VERSION_EXTRA: Option<&str> =
|
|||||||
v
|
v
|
||||||
} else if let v @ Some(_) = option_env!("CONDUWUIT_VERSION_EXTRA") {
|
} else if let v @ Some(_) = option_env!("CONDUWUIT_VERSION_EXTRA") {
|
||||||
v
|
v
|
||||||
} else if let v @ Some(_) = option_env!("CONDUIT_VERSION_EXTRA") {
|
|
||||||
v
|
|
||||||
} else {
|
} else {
|
||||||
GIT_COMMIT_HASH_SHORT
|
option_env!("CONDUIT_VERSION_EXTRA")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn version_tag() -> Option<&'static str> {
|
||||||
|
VERSION_EXTRA
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.or(GIT_COMMIT_HASH_SHORT)
|
||||||
|
}
|
||||||
|
|
||||||
pub static GIT_REMOTE_WEB_URL: Option<&str> = option_env!("GIT_REMOTE_WEB_URL");
|
pub static GIT_REMOTE_WEB_URL: Option<&str> = option_env!("GIT_REMOTE_WEB_URL");
|
||||||
pub static GIT_REMOTE_COMMIT_URL: Option<&str> = option_env!("GIT_REMOTE_COMMIT_URL");
|
pub static GIT_REMOTE_COMMIT_URL: Option<&str> = option_env!("GIT_REMOTE_COMMIT_URL");
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,10 @@ pub fn set_dirty_decay<I: Into<Option<usize>>>(arena: I, decay_ms: isize) -> Res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn background_thread_enable(enable: bool) -> Result<bool> {
|
||||||
|
set::<u8>(&mallctl!("background_thread"), enable.into()).map(is_nonzero!())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_affine_arena() -> bool { is_percpu_arena() || is_phycpu_arena() }
|
pub fn is_affine_arena() -> bool { is_percpu_arena() || is_phycpu_arena() }
|
||||||
|
|||||||
@@ -219,6 +219,15 @@ pub fn check(config: &Config) -> Result {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if support contact information is configured
|
||||||
|
if config.well_known.support_email.is_none() && config.well_known.support_mxid.is_none() {
|
||||||
|
warn!(
|
||||||
|
"No support contact information (support_email or support_mxid) is configured in \
|
||||||
|
the well_known section. Users in the admin room will be automatically listed as \
|
||||||
|
support contacts in the /.well-known/matrix/support endpoint."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if config
|
if config
|
||||||
.url_preview_domain_contains_allowlist
|
.url_preview_domain_contains_allowlist
|
||||||
.contains(&"*".to_owned())
|
.contains(&"*".to_owned())
|
||||||
|
|||||||
@@ -1897,12 +1897,28 @@ pub struct WellKnownConfig {
|
|||||||
/// example: "matrix.example.com:443"
|
/// example: "matrix.example.com:443"
|
||||||
pub server: Option<OwnedServerName>,
|
pub server: Option<OwnedServerName>,
|
||||||
|
|
||||||
|
/// URL to a support page for the server, which will be served as part of
|
||||||
|
/// the MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||||
|
/// Will be included alongside any contact information
|
||||||
pub support_page: Option<Url>,
|
pub support_page: Option<Url>,
|
||||||
|
|
||||||
|
/// Role string for server support contacts, to be served as part of the
|
||||||
|
/// MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||||
|
///
|
||||||
|
/// default: "m.role.admin"
|
||||||
pub support_role: Option<ContactRole>,
|
pub support_role: Option<ContactRole>,
|
||||||
|
|
||||||
|
/// Email address for server support contacts, to be served as part of the
|
||||||
|
/// MSC1929 server support endpoint.
|
||||||
|
/// This will be used along with support_mxid if specified.
|
||||||
pub support_email: Option<String>,
|
pub support_email: Option<String>,
|
||||||
|
|
||||||
|
/// Matrix ID for server support contacts, to be served as part of the
|
||||||
|
/// MSC1929 server support endpoint.
|
||||||
|
/// This will be used along with support_email if specified.
|
||||||
|
///
|
||||||
|
/// If no email or mxid is specified, all of the server's admins will be
|
||||||
|
/// listed.
|
||||||
pub support_mxid: Option<OwnedUserId>,
|
pub support_mxid: Option<OwnedUserId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
|||||||
fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
|
fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
|
||||||
|
|
||||||
fn init_version() -> String {
|
fn init_version() -> String {
|
||||||
conduwuit_build_metadata::VERSION_EXTRA
|
conduwuit_build_metadata::version_tag()
|
||||||
.map_or(SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
|
.map_or(SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
use std::{
|
|
||||||
borrow::Borrow,
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
hash::Hash,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ruma::{EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, events::TimelineEventType};
|
use ruma::{EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, events::TimelineEventType};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
/// Abstraction of a PDU so users can have their own PDU types.
|
/// Abstraction of a PDU so users can have their own PDU types.
|
||||||
pub trait Event {
|
pub trait Event {
|
||||||
type Id: Clone + Debug + Display + Eq + Ord + Hash + Send + Borrow<EventId>;
|
|
||||||
|
|
||||||
/// The `EventId` of this event.
|
/// The `EventId` of this event.
|
||||||
fn event_id(&self) -> &Self::Id;
|
fn event_id(&self) -> &EventId;
|
||||||
|
|
||||||
/// The `RoomId` of this event.
|
/// The `RoomId` of this event.
|
||||||
fn room_id(&self) -> &RoomId;
|
fn room_id(&self) -> &RoomId;
|
||||||
@@ -34,20 +26,18 @@ pub trait Event {
|
|||||||
|
|
||||||
/// The events before this event.
|
/// The events before this event.
|
||||||
// Requires GATs to avoid boxing (and TAIT for making it convenient).
|
// Requires GATs to avoid boxing (and TAIT for making it convenient).
|
||||||
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_;
|
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_;
|
||||||
|
|
||||||
/// All the authenticating events for this event.
|
/// All the authenticating events for this event.
|
||||||
// Requires GATs to avoid boxing (and TAIT for making it convenient).
|
// Requires GATs to avoid boxing (and TAIT for making it convenient).
|
||||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_;
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_;
|
||||||
|
|
||||||
/// If this event is a redaction event this is the event it redacts.
|
/// If this event is a redaction event this is the event it redacts.
|
||||||
fn redacts(&self) -> Option<&Self::Id>;
|
fn redacts(&self) -> Option<&EventId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Event> Event for &T {
|
impl<T: Event> Event for &T {
|
||||||
type Id = T::Id;
|
fn event_id(&self) -> &EventId { (*self).event_id() }
|
||||||
|
|
||||||
fn event_id(&self) -> &Self::Id { (*self).event_id() }
|
|
||||||
|
|
||||||
fn room_id(&self) -> &RoomId { (*self).room_id() }
|
fn room_id(&self) -> &RoomId { (*self).room_id() }
|
||||||
|
|
||||||
@@ -61,13 +51,13 @@ impl<T: Event> Event for &T {
|
|||||||
|
|
||||||
fn state_key(&self) -> Option<&str> { (*self).state_key() }
|
fn state_key(&self) -> Option<&str> { (*self).state_key() }
|
||||||
|
|
||||||
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
|
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
|
||||||
(*self).prev_events()
|
(*self).prev_events()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
|
||||||
(*self).auth_events()
|
(*self).auth_events()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redacts(&self) -> Option<&Self::Id> { (*self).redacts() }
|
fn redacts(&self) -> Option<&EventId> { (*self).redacts() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,9 +79,7 @@ impl Pdu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Event for Pdu {
|
impl Event for Pdu {
|
||||||
type Id = OwnedEventId;
|
fn event_id(&self) -> &EventId { &self.event_id }
|
||||||
|
|
||||||
fn event_id(&self) -> &Self::Id { &self.event_id }
|
|
||||||
|
|
||||||
fn room_id(&self) -> &RoomId { &self.room_id }
|
fn room_id(&self) -> &RoomId { &self.room_id }
|
||||||
|
|
||||||
@@ -97,15 +95,15 @@ impl Event for Pdu {
|
|||||||
|
|
||||||
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
|
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
|
||||||
|
|
||||||
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
|
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
|
||||||
self.prev_events.iter()
|
self.prev_events.iter().map(AsRef::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &Self::Id> + Send + '_ {
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
|
||||||
self.auth_events.iter()
|
self.auth_events.iter().map(AsRef::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redacts(&self) -> Option<&Self::Id> { self.redacts.as_ref() }
|
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prevent derived equality which wouldn't limit itself to event_id
|
/// Prevent derived equality which wouldn't limit itself to event_id
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
|
AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent,
|
||||||
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent,
|
AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, room::member::RoomMemberEventContent,
|
||||||
room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent,
|
space::child::HierarchySpaceChildEvent,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
};
|
};
|
||||||
@@ -10,41 +10,6 @@ use serde_json::{json, value::Value as JsonValue};
|
|||||||
|
|
||||||
use crate::implement;
|
use crate::implement;
|
||||||
|
|
||||||
/// This only works for events that are also AnyRoomEvents.
|
|
||||||
#[must_use]
|
|
||||||
#[implement(super::Pdu)]
|
|
||||||
pub fn into_any_event(self) -> Raw<AnyEphemeralRoomEvent> {
|
|
||||||
serde_json::from_value(self.into_any_event_value()).expect("Raw::from_value always works")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This only works for events that are also AnyRoomEvents.
|
|
||||||
#[implement(super::Pdu)]
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn into_any_event_value(self) -> JsonValue {
|
|
||||||
let (redacts, content) = self.copy_redacts();
|
|
||||||
let mut json = json!({
|
|
||||||
"content": content,
|
|
||||||
"type": self.kind,
|
|
||||||
"event_id": self.event_id,
|
|
||||||
"sender": self.sender,
|
|
||||||
"origin_server_ts": self.origin_server_ts,
|
|
||||||
"room_id": self.room_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(unsigned) = &self.unsigned {
|
|
||||||
json["unsigned"] = json!(unsigned);
|
|
||||||
}
|
|
||||||
if let Some(state_key) = &self.state_key {
|
|
||||||
json["state_key"] = json!(state_key);
|
|
||||||
}
|
|
||||||
if let Some(redacts) = &redacts {
|
|
||||||
json["redacts"] = json!(redacts);
|
|
||||||
}
|
|
||||||
|
|
||||||
json
|
|
||||||
}
|
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -53,7 +18,8 @@ pub fn into_room_event(self) -> Raw<AnyTimelineEvent> { self.to_room_event() }
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_room_event(&self) -> Raw<AnyTimelineEvent> {
|
pub fn to_room_event(&self) -> Raw<AnyTimelineEvent> {
|
||||||
serde_json::from_value(self.to_room_event_value()).expect("Raw::from_value always works")
|
let value = self.to_room_event_value();
|
||||||
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -91,8 +57,8 @@ pub fn into_message_like_event(self) -> Raw<AnyMessageLikeEvent> { self.to_messa
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
|
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
|
||||||
serde_json::from_value(self.to_message_like_event_value())
|
let value = self.to_message_like_event_value();
|
||||||
.expect("Raw::from_value always works")
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -130,7 +96,8 @@ pub fn into_sync_room_event(self) -> Raw<AnySyncTimelineEvent> { self.to_sync_ro
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
|
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
|
||||||
serde_json::from_value(self.to_sync_room_event_value()).expect("Raw::from_value always works")
|
let value = self.to_sync_room_event_value();
|
||||||
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -162,7 +129,8 @@ pub fn to_sync_room_event_value(&self) -> JsonValue {
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_state_event(self) -> Raw<AnyStateEvent> {
|
pub fn into_state_event(self) -> Raw<AnyStateEvent> {
|
||||||
serde_json::from_value(self.into_state_event_value()).expect("Raw::from_value always works")
|
let value = self.into_state_event_value();
|
||||||
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -189,8 +157,8 @@ pub fn into_state_event_value(self) -> JsonValue {
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_sync_state_event(self) -> Raw<AnySyncStateEvent> {
|
pub fn into_sync_state_event(self) -> Raw<AnySyncStateEvent> {
|
||||||
serde_json::from_value(self.into_sync_state_event_value())
|
let value = self.into_sync_state_event_value();
|
||||||
.expect("Raw::from_value always works")
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -223,8 +191,8 @@ pub fn into_stripped_state_event(self) -> Raw<AnyStrippedStateEvent> {
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
|
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
|
||||||
serde_json::from_value(self.to_stripped_state_event_value())
|
let value = self.to_stripped_state_event_value();
|
||||||
.expect("Raw::from_value always works")
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -242,8 +210,8 @@ pub fn to_stripped_state_event_value(&self) -> JsonValue {
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_stripped_spacechild_state_event(self) -> Raw<HierarchySpaceChildEvent> {
|
pub fn into_stripped_spacechild_state_event(self) -> Raw<HierarchySpaceChildEvent> {
|
||||||
serde_json::from_value(self.into_stripped_spacechild_state_event_value())
|
let value = self.into_stripped_spacechild_state_event_value();
|
||||||
.expect("Raw::from_value always works")
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
@@ -262,7 +230,8 @@ pub fn into_stripped_spacechild_state_event_value(self) -> JsonValue {
|
|||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_member_event(self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
pub fn into_member_event(self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
||||||
serde_json::from_value(self.into_member_event_value()).expect("Raw::from_value always works")
|
let value = self.into_member_event_value();
|
||||||
|
serde_json::from_value(value).expect("Failed to serialize Event value")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Pdu)]
|
#[implement(super::Pdu)]
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ fn lexico_topo_sort(c: &mut test::Bencher) {
|
|||||||
#[cfg(conduwuit_bench)]
|
#[cfg(conduwuit_bench)]
|
||||||
#[cfg_attr(conduwuit_bench, bench)]
|
#[cfg_attr(conduwuit_bench, bench)]
|
||||||
fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
|
fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
|
||||||
let parallel_fetches = 32;
|
|
||||||
let mut store = TestStore(hashmap! {});
|
let mut store = TestStore(hashmap! {});
|
||||||
|
|
||||||
// build up the DAG
|
// build up the DAG
|
||||||
@@ -78,7 +77,6 @@ fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
|
|||||||
&auth_chain_sets,
|
&auth_chain_sets,
|
||||||
&fetch,
|
&fetch,
|
||||||
&exists,
|
&exists,
|
||||||
parallel_fetches,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -91,7 +89,6 @@ fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
|
|||||||
#[cfg(conduwuit_bench)]
|
#[cfg(conduwuit_bench)]
|
||||||
#[cfg_attr(conduwuit_bench, bench)]
|
#[cfg_attr(conduwuit_bench, bench)]
|
||||||
fn resolve_deeper_event_set(c: &mut test::Bencher) {
|
fn resolve_deeper_event_set(c: &mut test::Bencher) {
|
||||||
let parallel_fetches = 32;
|
|
||||||
let mut inner = INITIAL_EVENTS();
|
let mut inner = INITIAL_EVENTS();
|
||||||
let ban = BAN_STATE_SET();
|
let ban = BAN_STATE_SET();
|
||||||
|
|
||||||
@@ -153,7 +150,6 @@ fn resolve_deeper_event_set(c: &mut test::Bencher) {
|
|||||||
&auth_chain_sets,
|
&auth_chain_sets,
|
||||||
&fetch,
|
&fetch,
|
||||||
&exists,
|
&exists,
|
||||||
parallel_fetches,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -190,7 +186,11 @@ impl<E: Event + Clone> TestStore<E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Vec of the related auth events to the given `event`.
|
/// 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>> {
|
fn auth_event_ids(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
event_ids: Vec<OwnedEventId>,
|
||||||
|
) -> Result<HashSet<OwnedEventId>> {
|
||||||
let mut result = HashSet::new();
|
let mut result = HashSet::new();
|
||||||
let mut stack = event_ids;
|
let mut stack = event_ids;
|
||||||
|
|
||||||
@@ -216,8 +216,8 @@ impl<E: Event + Clone> TestStore<E> {
|
|||||||
fn auth_chain_diff(
|
fn auth_chain_diff(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
event_ids: Vec<Vec<E::Id>>,
|
event_ids: Vec<Vec<OwnedEventId>>,
|
||||||
) -> Result<Vec<E::Id>> {
|
) -> Result<Vec<OwnedEventId>> {
|
||||||
let mut auth_chain_sets = vec![];
|
let mut auth_chain_sets = vec![];
|
||||||
for ids in event_ids {
|
for ids in event_ids {
|
||||||
// TODO state store `auth_event_ids` returns self in the event ids list
|
// TODO state store `auth_event_ids` returns self in the event ids list
|
||||||
@@ -238,7 +238,7 @@ impl<E: Event + Clone> TestStore<E> {
|
|||||||
Ok(auth_chain_sets
|
Ok(auth_chain_sets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|id| !common.contains(id.borrow()))
|
.filter(|id| !common.contains(id))
|
||||||
.collect())
|
.collect())
|
||||||
} else {
|
} else {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
@@ -565,7 +565,7 @@ impl EventTypeExt for &TimelineEventType {
|
|||||||
|
|
||||||
mod event {
|
mod event {
|
||||||
use ruma::{
|
use ruma::{
|
||||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
||||||
events::{TimelineEventType, pdu::Pdu},
|
events::{TimelineEventType, pdu::Pdu},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -574,9 +574,7 @@ mod event {
|
|||||||
use super::Event;
|
use super::Event;
|
||||||
|
|
||||||
impl Event for PduEvent {
|
impl Event for PduEvent {
|
||||||
type Id = OwnedEventId;
|
fn event_id(&self) -> &EventId { &self.event_id }
|
||||||
|
|
||||||
fn event_id(&self) -> &Self::Id { &self.event_id }
|
|
||||||
|
|
||||||
fn room_id(&self) -> &RoomId {
|
fn room_id(&self) -> &RoomId {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
@@ -632,28 +630,30 @@ mod event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
|
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => Box::new(ev.prev_events.iter().map(|(id, _)| id)),
|
| Pdu::RoomV1Pdu(ev) =>
|
||||||
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter()),
|
Box::new(ev.prev_events.iter().map(|(id, _)| id.as_ref())),
|
||||||
|
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter().map(AsRef::as_ref)),
|
||||||
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
|
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => Box::new(ev.auth_events.iter().map(|(id, _)| id)),
|
| Pdu::RoomV1Pdu(ev) =>
|
||||||
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter()),
|
Box::new(ev.auth_events.iter().map(|(id, _)| id.as_ref())),
|
||||||
|
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter().map(AsRef::as_ref)),
|
||||||
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redacts(&self) -> Option<&Self::Id> {
|
fn redacts(&self) -> Option<&EventId> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
|
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_deref(),
|
||||||
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
|
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_deref(),
|
||||||
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
#[cfg(not(feature = "unstable-exhaustive-types"))]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ pub fn auth_types_for_event(
|
|||||||
level = "debug",
|
level = "debug",
|
||||||
skip_all,
|
skip_all,
|
||||||
fields(
|
fields(
|
||||||
event_id = incoming_event.event_id().borrow().as_str()
|
event_id = incoming_event.event_id().as_str(),
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn auth_check<F, Fut, Fetched, Incoming>(
|
pub async fn auth_check<F, Fut, Fetched, Incoming>(
|
||||||
@@ -259,7 +259,7 @@ where
|
|||||||
// 3. If event does not have m.room.create in auth_events reject
|
// 3. If event does not have m.room.create in auth_events reject
|
||||||
if !incoming_event
|
if !incoming_event
|
||||||
.auth_events()
|
.auth_events()
|
||||||
.any(|id| id.borrow() == room_create_event.event_id().borrow())
|
.any(|id| id == room_create_event.event_id())
|
||||||
{
|
{
|
||||||
warn!("no m.room.create event in auth events");
|
warn!("no m.room.create event in auth events");
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@@ -638,7 +638,7 @@ fn valid_membership_change(
|
|||||||
warn!(?target_user_membership_event_id, "Banned user can't join");
|
warn!(?target_user_membership_event_id, "Banned user can't join");
|
||||||
false
|
false
|
||||||
} else if (join_rules == JoinRule::Invite
|
} else if (join_rules == JoinRule::Invite
|
||||||
|| room_version.allow_knocking && join_rules == JoinRule::Knock)
|
|| room_version.allow_knocking && (join_rules == JoinRule::Knock || matches!(join_rules, JoinRule::KnockRestricted(_))))
|
||||||
// If the join_rule is invite then allow if membership state is invite or join
|
// If the join_rule is invite then allow if membership state is invite or join
|
||||||
&& (target_user_current_membership == MembershipState::Join
|
&& (target_user_current_membership == MembershipState::Join
|
||||||
|| target_user_current_membership == MembershipState::Invite)
|
|| target_user_current_membership == MembershipState::Invite)
|
||||||
@@ -1021,11 +1021,11 @@ fn check_redaction(
|
|||||||
|
|
||||||
// If the domain of the event_id of the event being redacted is the same as the
|
// If the domain of the event_id of the event being redacted is the same as the
|
||||||
// domain of the event_id of the m.room.redaction, allow
|
// domain of the event_id of the m.room.redaction, allow
|
||||||
if redaction_event.event_id().borrow().server_name()
|
if redaction_event.event_id().server_name()
|
||||||
== redaction_event
|
== redaction_event
|
||||||
.redacts()
|
.redacts()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|&id| id.borrow().server_name())
|
.and_then(|&id| id.server_name())
|
||||||
{
|
{
|
||||||
debug!("redaction event allowed via room version 1 rules");
|
debug!("redaction event allowed via room version 1 rules");
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use std::{
|
|||||||
|
|
||||||
use futures::{Future, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future};
|
use futures::{Future, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
EventId, Int, MilliSecondsSinceUnixEpoch, RoomVersionId,
|
EventId, Int, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId,
|
||||||
events::{
|
events::{
|
||||||
StateEventType, TimelineEventType,
|
StateEventType, TimelineEventType,
|
||||||
room::member::{MembershipState, RoomMemberEventContent},
|
room::member::{MembershipState, RoomMemberEventContent},
|
||||||
@@ -39,9 +39,7 @@ use crate::{
|
|||||||
debug, debug_error,
|
debug, debug_error,
|
||||||
matrix::{event::Event, pdu::StateKey},
|
matrix::{event::Event, pdu::StateKey},
|
||||||
trace,
|
trace,
|
||||||
utils::stream::{
|
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt},
|
||||||
BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryReadyExt, WidebandExt,
|
|
||||||
},
|
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,9 +67,6 @@ type Result<T, E = Error> = crate::Result<T, E>;
|
|||||||
/// * `event_fetch` - Any event not found in the `event_map` will defer to this
|
/// * `event_fetch` - Any event not found in the `event_map` will defer to this
|
||||||
/// closure to find the event.
|
/// closure to find the event.
|
||||||
///
|
///
|
||||||
/// * `parallel_fetches` - The number of asynchronous fetch requests in-flight
|
|
||||||
/// for any given operation.
|
|
||||||
///
|
|
||||||
/// ## Invariants
|
/// ## Invariants
|
||||||
///
|
///
|
||||||
/// The caller of `resolve` must ensure that all the events are from the same
|
/// The caller of `resolve` must ensure that all the events are from the same
|
||||||
@@ -82,21 +77,19 @@ type Result<T, E = Error> = crate::Result<T, E>;
|
|||||||
pub async fn resolve<'a, E, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>(
|
pub async fn resolve<'a, E, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>(
|
||||||
room_version: &RoomVersionId,
|
room_version: &RoomVersionId,
|
||||||
state_sets: Sets,
|
state_sets: Sets,
|
||||||
auth_chain_sets: &'a [HashSet<E::Id, Hasher>],
|
auth_chain_sets: &'a [HashSet<OwnedEventId, Hasher>],
|
||||||
event_fetch: &Fetch,
|
event_fetch: &Fetch,
|
||||||
event_exists: &Exists,
|
event_exists: &Exists,
|
||||||
parallel_fetches: usize,
|
) -> Result<StateMap<OwnedEventId>>
|
||||||
) -> Result<StateMap<E::Id>>
|
|
||||||
where
|
where
|
||||||
Fetch: Fn(E::Id) -> FetchFut + Sync,
|
Fetch: Fn(OwnedEventId) -> FetchFut + Sync,
|
||||||
FetchFut: Future<Output = Option<E>> + Send,
|
FetchFut: Future<Output = Option<E>> + Send,
|
||||||
Exists: Fn(E::Id) -> ExistsFut + Sync,
|
Exists: Fn(OwnedEventId) -> ExistsFut + Sync,
|
||||||
ExistsFut: Future<Output = bool> + Send,
|
ExistsFut: Future<Output = bool> + Send,
|
||||||
Sets: IntoIterator<IntoIter = SetIter> + Send,
|
Sets: IntoIterator<IntoIter = SetIter> + Send,
|
||||||
SetIter: Iterator<Item = &'a StateMap<E::Id>> + Clone + Send,
|
SetIter: Iterator<Item = &'a StateMap<OwnedEventId>> + Clone + Send,
|
||||||
Hasher: BuildHasher + Send + Sync,
|
Hasher: BuildHasher + Send + Sync,
|
||||||
E: Event + Clone + Send + Sync,
|
E: Event + Clone + Send + Sync,
|
||||||
E::Id: Borrow<EventId> + Send + Sync,
|
|
||||||
for<'b> &'b E: Send,
|
for<'b> &'b E: Send,
|
||||||
{
|
{
|
||||||
debug!("State resolution starting");
|
debug!("State resolution starting");
|
||||||
@@ -147,13 +140,8 @@ where
|
|||||||
|
|
||||||
// Sort the control events based on power_level/clock/event_id and
|
// Sort the control events based on power_level/clock/event_id and
|
||||||
// outgoing/incoming edges
|
// outgoing/incoming edges
|
||||||
let sorted_control_levels = reverse_topological_power_sort(
|
let sorted_control_levels =
|
||||||
control_events,
|
reverse_topological_power_sort(control_events, &all_conflicted, &event_fetch).await?;
|
||||||
&all_conflicted,
|
|
||||||
&event_fetch,
|
|
||||||
parallel_fetches,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
debug!(count = sorted_control_levels.len(), "power events");
|
debug!(count = sorted_control_levels.len(), "power events");
|
||||||
trace!(list = ?sorted_control_levels, "sorted power events");
|
trace!(list = ?sorted_control_levels, "sorted power events");
|
||||||
@@ -162,7 +150,7 @@ where
|
|||||||
// Sequentially auth check each control event.
|
// Sequentially auth check each control event.
|
||||||
let resolved_control = iterative_auth_check(
|
let resolved_control = iterative_auth_check(
|
||||||
&room_version,
|
&room_version,
|
||||||
sorted_control_levels.iter().stream(),
|
sorted_control_levels.iter().stream().map(AsRef::as_ref),
|
||||||
clean.clone(),
|
clean.clone(),
|
||||||
&event_fetch,
|
&event_fetch,
|
||||||
)
|
)
|
||||||
@@ -179,7 +167,7 @@ where
|
|||||||
// that failed auth
|
// that failed auth
|
||||||
let events_to_resolve: Vec<_> = all_conflicted
|
let events_to_resolve: Vec<_> = all_conflicted
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&id| !deduped_power_ev.contains(id.borrow()))
|
.filter(|&id| !deduped_power_ev.contains(id))
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -199,7 +187,7 @@ where
|
|||||||
|
|
||||||
let mut resolved_state = iterative_auth_check(
|
let mut resolved_state = iterative_auth_check(
|
||||||
&room_version,
|
&room_version,
|
||||||
sorted_left_events.iter().stream(),
|
sorted_left_events.iter().stream().map(AsRef::as_ref),
|
||||||
resolved_control, // The control events are added to the final resolved state
|
resolved_control, // The control events are added to the final resolved state
|
||||||
&event_fetch,
|
&event_fetch,
|
||||||
)
|
)
|
||||||
@@ -292,16 +280,14 @@ where
|
|||||||
/// earlier (further back in time) origin server timestamp.
|
/// earlier (further back in time) origin server timestamp.
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn reverse_topological_power_sort<E, F, Fut>(
|
async fn reverse_topological_power_sort<E, F, Fut>(
|
||||||
events_to_sort: Vec<E::Id>,
|
events_to_sort: Vec<OwnedEventId>,
|
||||||
auth_diff: &HashSet<E::Id>,
|
auth_diff: &HashSet<OwnedEventId>,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
parallel_fetches: usize,
|
) -> Result<Vec<OwnedEventId>>
|
||||||
) -> Result<Vec<E::Id>>
|
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Send + Sync,
|
E: Event + Send + Sync,
|
||||||
E::Id: Borrow<EventId> + Send + Sync,
|
|
||||||
{
|
{
|
||||||
debug!("reverse topological sort of power events");
|
debug!("reverse topological sort of power events");
|
||||||
|
|
||||||
@@ -311,35 +297,36 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is used in the `key_fn` passed to the lexico_topo_sort fn
|
// This is used in the `key_fn` passed to the lexico_topo_sort fn
|
||||||
let event_to_pl = graph
|
let event_to_pl: HashMap<_, _> = graph
|
||||||
.keys()
|
.keys()
|
||||||
|
.cloned()
|
||||||
.stream()
|
.stream()
|
||||||
.map(|event_id| {
|
.broad_filter_map(async |event_id| {
|
||||||
get_power_level_for_sender(event_id.clone(), fetch_event)
|
let pl = get_power_level_for_sender(&event_id, fetch_event)
|
||||||
.map(move |res| res.map(|pl| (event_id, pl)))
|
.await
|
||||||
|
.ok()?;
|
||||||
|
Some((event_id, pl))
|
||||||
})
|
})
|
||||||
.buffer_unordered(parallel_fetches)
|
.inspect(|(event_id, pl)| {
|
||||||
.ready_try_fold(HashMap::new(), |mut event_to_pl, (event_id, pl)| {
|
|
||||||
debug!(
|
debug!(
|
||||||
event_id = event_id.borrow().as_str(),
|
event_id = event_id.as_str(),
|
||||||
power_level = i64::from(pl),
|
power_level = i64::from(*pl),
|
||||||
"found the power level of an event's sender",
|
"found the power level of an event's sender",
|
||||||
);
|
);
|
||||||
|
|
||||||
event_to_pl.insert(event_id.clone(), pl);
|
|
||||||
Ok(event_to_pl)
|
|
||||||
})
|
})
|
||||||
|
.collect()
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await;
|
||||||
|
|
||||||
let event_to_pl = &event_to_pl;
|
let fetcher = async |event_id: OwnedEventId| {
|
||||||
let fetcher = |event_id: E::Id| async move {
|
|
||||||
let pl = *event_to_pl
|
let pl = *event_to_pl
|
||||||
.get(event_id.borrow())
|
.get(&event_id)
|
||||||
.ok_or_else(|| Error::NotFound(String::new()))?;
|
.ok_or_else(|| Error::NotFound(String::new()))?;
|
||||||
|
|
||||||
let ev = fetch_event(event_id)
|
let ev = fetch_event(event_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| Error::NotFound(String::new()))?;
|
.ok_or_else(|| Error::NotFound(String::new()))?;
|
||||||
|
|
||||||
Ok((pl, ev.origin_server_ts()))
|
Ok((pl, ev.origin_server_ts()))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -476,18 +463,17 @@ where
|
|||||||
/// the eventId at the eventId's generation (we walk backwards to `EventId`s
|
/// the eventId at the eventId's generation (we walk backwards to `EventId`s
|
||||||
/// most recent previous power level event).
|
/// most recent previous power level event).
|
||||||
async fn get_power_level_for_sender<E, F, Fut>(
|
async fn get_power_level_for_sender<E, F, Fut>(
|
||||||
event_id: E::Id,
|
event_id: &EventId,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
) -> serde_json::Result<Int>
|
) -> serde_json::Result<Int>
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Send,
|
E: Event + Send,
|
||||||
E::Id: Borrow<EventId> + Send,
|
|
||||||
{
|
{
|
||||||
debug!("fetch event ({event_id}) senders power level");
|
debug!("fetch event ({event_id}) senders power level");
|
||||||
|
|
||||||
let event = fetch_event(event_id).await;
|
let event = fetch_event(event_id.to_owned()).await;
|
||||||
|
|
||||||
let auth_events = event.as_ref().map(Event::auth_events);
|
let auth_events = event.as_ref().map(Event::auth_events);
|
||||||
|
|
||||||
@@ -495,7 +481,7 @@ where
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.stream()
|
.stream()
|
||||||
.broadn_filter_map(5, |aid| fetch_event(aid.clone()))
|
.broadn_filter_map(5, |aid| fetch_event(aid.to_owned()))
|
||||||
.ready_find(|aev| is_type_and_key(aev, &TimelineEventType::RoomPowerLevels, ""))
|
.ready_find(|aev| is_type_and_key(aev, &TimelineEventType::RoomPowerLevels, ""))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -528,14 +514,13 @@ where
|
|||||||
async fn iterative_auth_check<'a, E, F, Fut, S>(
|
async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||||
room_version: &RoomVersion,
|
room_version: &RoomVersion,
|
||||||
events_to_check: S,
|
events_to_check: S,
|
||||||
unconflicted_state: StateMap<E::Id>,
|
unconflicted_state: StateMap<OwnedEventId>,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
) -> Result<StateMap<E::Id>>
|
) -> Result<StateMap<OwnedEventId>>
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E::Id: Borrow<EventId> + Clone + Eq + Ord + Send + Sync + 'a,
|
S: Stream<Item = &'a EventId> + Send + 'a,
|
||||||
S: Stream<Item = &'a E::Id> + Send + 'a,
|
|
||||||
E: Event + Clone + Send + Sync,
|
E: Event + Clone + Send + Sync,
|
||||||
{
|
{
|
||||||
debug!("starting iterative auth check");
|
debug!("starting iterative auth check");
|
||||||
@@ -543,7 +528,7 @@ where
|
|||||||
let events_to_check: Vec<_> = events_to_check
|
let events_to_check: Vec<_> = events_to_check
|
||||||
.map(Result::Ok)
|
.map(Result::Ok)
|
||||||
.broad_and_then(async |event_id| {
|
.broad_and_then(async |event_id| {
|
||||||
fetch_event(event_id.clone())
|
fetch_event(event_id.to_owned())
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}")))
|
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}")))
|
||||||
})
|
})
|
||||||
@@ -551,16 +536,16 @@ where
|
|||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let auth_event_ids: HashSet<E::Id> = events_to_check
|
let auth_event_ids: HashSet<OwnedEventId> = events_to_check
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|event: &E| event.auth_events().map(Clone::clone))
|
.flat_map(|event: &E| event.auth_events().map(ToOwned::to_owned))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let auth_events: HashMap<E::Id, E> = auth_event_ids
|
let auth_events: HashMap<OwnedEventId, E> = auth_event_ids
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.stream()
|
.stream()
|
||||||
.broad_filter_map(fetch_event)
|
.broad_filter_map(fetch_event)
|
||||||
.map(|auth_event| (auth_event.event_id().clone(), auth_event))
|
.map(|auth_event| (auth_event.event_id().to_owned(), auth_event))
|
||||||
.collect()
|
.collect()
|
||||||
.boxed()
|
.boxed()
|
||||||
.await;
|
.await;
|
||||||
@@ -581,7 +566,7 @@ where
|
|||||||
|
|
||||||
let mut auth_state = StateMap::new();
|
let mut auth_state = StateMap::new();
|
||||||
for aid in event.auth_events() {
|
for aid in event.auth_events() {
|
||||||
if let Some(ev) = auth_events.get(aid.borrow()) {
|
if let Some(ev) = auth_events.get(aid) {
|
||||||
//TODO: synapse checks "rejected_reason" which is most likely related to
|
//TODO: synapse checks "rejected_reason" which is most likely related to
|
||||||
// soft-failing
|
// soft-failing
|
||||||
auth_state.insert(
|
auth_state.insert(
|
||||||
@@ -592,7 +577,7 @@ where
|
|||||||
ev.clone(),
|
ev.clone(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warn!(event_id = aid.borrow().as_str(), "missing auth event");
|
warn!(event_id = aid.as_str(), "missing auth event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,7 +586,7 @@ where
|
|||||||
.stream()
|
.stream()
|
||||||
.ready_filter_map(|key| Some((key, resolved_state.get(key)?)))
|
.ready_filter_map(|key| Some((key, resolved_state.get(key)?)))
|
||||||
.filter_map(|(key, ev_id)| async move {
|
.filter_map(|(key, ev_id)| async move {
|
||||||
if let Some(event) = auth_events.get(ev_id.borrow()) {
|
if let Some(event) = auth_events.get(ev_id) {
|
||||||
Some((key, event.clone()))
|
Some((key, event.clone()))
|
||||||
} else {
|
} else {
|
||||||
Some((key, fetch_event(ev_id.clone()).await?))
|
Some((key, fetch_event(ev_id.clone()).await?))
|
||||||
@@ -633,7 +618,7 @@ where
|
|||||||
// add event to resolved state map
|
// add event to resolved state map
|
||||||
resolved_state.insert(
|
resolved_state.insert(
|
||||||
event.event_type().with_state_key(state_key),
|
event.event_type().with_state_key(state_key),
|
||||||
event.event_id().clone(),
|
event.event_id().to_owned(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
| Ok(false) => {
|
| Ok(false) => {
|
||||||
@@ -660,15 +645,14 @@ where
|
|||||||
/// level as a parent) will be marked as depth 1. depth 1 is "older" than depth
|
/// level as a parent) will be marked as depth 1. depth 1 is "older" than depth
|
||||||
/// 0.
|
/// 0.
|
||||||
async fn mainline_sort<E, F, Fut>(
|
async fn mainline_sort<E, F, Fut>(
|
||||||
to_sort: &[E::Id],
|
to_sort: &[OwnedEventId],
|
||||||
resolved_power_level: Option<E::Id>,
|
resolved_power_level: Option<OwnedEventId>,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
) -> Result<Vec<E::Id>>
|
) -> Result<Vec<OwnedEventId>>
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Clone + Send + Sync,
|
E: Event + Clone + Send + Sync,
|
||||||
E::Id: Borrow<EventId> + Clone + Send + Sync,
|
|
||||||
{
|
{
|
||||||
debug!("mainline sort of events");
|
debug!("mainline sort of events");
|
||||||
|
|
||||||
@@ -688,7 +672,7 @@ where
|
|||||||
|
|
||||||
pl = None;
|
pl = None;
|
||||||
for aid in event.auth_events() {
|
for aid in event.auth_events() {
|
||||||
let ev = fetch_event(aid.clone())
|
let ev = fetch_event(aid.to_owned())
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
||||||
|
|
||||||
@@ -734,26 +718,25 @@ where
|
|||||||
/// that has an associated mainline depth.
|
/// that has an associated mainline depth.
|
||||||
async fn get_mainline_depth<E, F, Fut>(
|
async fn get_mainline_depth<E, F, Fut>(
|
||||||
mut event: Option<E>,
|
mut event: Option<E>,
|
||||||
mainline_map: &HashMap<E::Id, usize>,
|
mainline_map: &HashMap<OwnedEventId, usize>,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
) -> Result<usize>
|
) -> Result<usize>
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Send + Sync,
|
E: Event + Send + Sync,
|
||||||
E::Id: Borrow<EventId> + Send + Sync,
|
|
||||||
{
|
{
|
||||||
while let Some(sort_ev) = event {
|
while let Some(sort_ev) = event {
|
||||||
debug!(event_id = sort_ev.event_id().borrow().as_str(), "mainline");
|
debug!(event_id = sort_ev.event_id().as_str(), "mainline");
|
||||||
|
|
||||||
let id = sort_ev.event_id();
|
let id = sort_ev.event_id();
|
||||||
if let Some(depth) = mainline_map.get(id.borrow()) {
|
if let Some(depth) = mainline_map.get(id) {
|
||||||
return Ok(*depth);
|
return Ok(*depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
event = None;
|
event = None;
|
||||||
for aid in sort_ev.auth_events() {
|
for aid in sort_ev.auth_events() {
|
||||||
let aev = fetch_event(aid.clone())
|
let aev = fetch_event(aid.to_owned())
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
||||||
|
|
||||||
@@ -768,15 +751,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
|
async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
|
||||||
graph: &mut HashMap<E::Id, HashSet<E::Id>>,
|
graph: &mut HashMap<OwnedEventId, HashSet<OwnedEventId>>,
|
||||||
event_id: E::Id,
|
event_id: OwnedEventId,
|
||||||
auth_diff: &HashSet<E::Id>,
|
auth_diff: &HashSet<OwnedEventId>,
|
||||||
fetch_event: &F,
|
fetch_event: &F,
|
||||||
) where
|
) where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Send + Sync,
|
E: Event + Send + Sync,
|
||||||
E::Id: Borrow<EventId> + Clone + Send + Sync,
|
|
||||||
{
|
{
|
||||||
let mut state = vec![event_id];
|
let mut state = vec![event_id];
|
||||||
while let Some(eid) = state.pop() {
|
while let Some(eid) = state.pop() {
|
||||||
@@ -786,26 +768,27 @@ async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
|
|||||||
|
|
||||||
// Prefer the store to event as the store filters dedups the events
|
// Prefer the store to event as the store filters dedups the events
|
||||||
for aid in auth_events {
|
for aid in auth_events {
|
||||||
if auth_diff.contains(aid.borrow()) {
|
if auth_diff.contains(aid) {
|
||||||
if !graph.contains_key(aid.borrow()) {
|
if !graph.contains_key(aid) {
|
||||||
state.push(aid.to_owned());
|
state.push(aid.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We just inserted this at the start of the while loop
|
graph
|
||||||
graph.get_mut(eid.borrow()).unwrap().insert(aid.to_owned());
|
.get_mut(&eid)
|
||||||
|
.expect("We just inserted this at the start of the while loop")
|
||||||
|
.insert(aid.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_power_event_id<E, F, Fut>(event_id: &E::Id, fetch: &F) -> bool
|
async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool
|
||||||
where
|
where
|
||||||
F: Fn(E::Id) -> Fut + Sync,
|
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||||
Fut: Future<Output = Option<E>> + Send,
|
Fut: Future<Output = Option<E>> + Send,
|
||||||
E: Event + Send,
|
E: Event + Send,
|
||||||
E::Id: Borrow<EventId> + Send + Sync,
|
|
||||||
{
|
{
|
||||||
match fetch(event_id.clone()).await.as_ref() {
|
match fetch(event_id.to_owned()).await.as_ref() {
|
||||||
| Some(state) => is_power_event(state),
|
| Some(state) => is_power_event(state),
|
||||||
| _ => false,
|
| _ => false,
|
||||||
}
|
}
|
||||||
@@ -909,13 +892,13 @@ mod tests {
|
|||||||
|
|
||||||
let fetcher = |id| ready(events.get(&id).cloned());
|
let fetcher = |id| ready(events.get(&id).cloned());
|
||||||
let sorted_power_events =
|
let sorted_power_events =
|
||||||
super::reverse_topological_power_sort(power_events, &auth_chain, &fetcher, 1)
|
super::reverse_topological_power_sort(power_events, &auth_chain, &fetcher)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let resolved_power = super::iterative_auth_check(
|
let resolved_power = super::iterative_auth_check(
|
||||||
&RoomVersion::V6,
|
&RoomVersion::V6,
|
||||||
sorted_power_events.iter().stream(),
|
sorted_power_events.iter().map(AsRef::as_ref).stream(),
|
||||||
HashMap::new(), // unconflicted events
|
HashMap::new(), // unconflicted events
|
||||||
&fetcher,
|
&fetcher,
|
||||||
)
|
)
|
||||||
@@ -1300,7 +1283,7 @@ mod tests {
|
|||||||
let ev_map = store.0.clone();
|
let ev_map = store.0.clone();
|
||||||
let fetcher = |id| ready(ev_map.get(&id).cloned());
|
let fetcher = |id| ready(ev_map.get(&id).cloned());
|
||||||
|
|
||||||
let exists = |id: <PduEvent as Event>::Id| ready(ev_map.get(&*id).is_some());
|
let exists = |id: OwnedEventId| ready(ev_map.get(&*id).is_some());
|
||||||
|
|
||||||
let state_sets = [state_at_bob, state_at_charlie];
|
let state_sets = [state_at_bob, state_at_charlie];
|
||||||
let auth_chain: Vec<_> = state_sets
|
let auth_chain: Vec<_> = state_sets
|
||||||
@@ -1312,19 +1295,13 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let resolved = match super::resolve(
|
let resolved =
|
||||||
&RoomVersionId::V2,
|
match super::resolve(&RoomVersionId::V2, &state_sets, &auth_chain, &fetcher, &exists)
|
||||||
&state_sets,
|
.await
|
||||||
&auth_chain,
|
{
|
||||||
&fetcher,
|
| Ok(state) => state,
|
||||||
&exists,
|
| Err(e) => panic!("{e}"),
|
||||||
1,
|
};
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Ok(state) => state,
|
|
||||||
| Err(e) => panic!("{e}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(expected, resolved);
|
assert_eq!(expected, resolved);
|
||||||
}
|
}
|
||||||
@@ -1429,21 +1406,15 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let fetcher = |id: <PduEvent as Event>::Id| ready(ev_map.get(&id).cloned());
|
let fetcher = |id: OwnedEventId| ready(ev_map.get(&id).cloned());
|
||||||
let exists = |id: <PduEvent as Event>::Id| ready(ev_map.get(&id).is_some());
|
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
|
||||||
let resolved = match super::resolve(
|
let resolved =
|
||||||
&RoomVersionId::V6,
|
match super::resolve(&RoomVersionId::V6, &state_sets, &auth_chain, &fetcher, &exists)
|
||||||
&state_sets,
|
.await
|
||||||
&auth_chain,
|
{
|
||||||
&fetcher,
|
| Ok(state) => state,
|
||||||
&exists,
|
| Err(e) => panic!("{e}"),
|
||||||
1,
|
};
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Ok(state) => state,
|
|
||||||
| Err(e) => panic!("{e}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
resolved = ?resolved
|
resolved = ?resolved
|
||||||
|
|||||||
@@ -133,17 +133,11 @@ pub(crate) async fn do_check(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let event_map = &event_map;
|
let event_map = &event_map;
|
||||||
let fetch = |id: <PduEvent as Event>::Id| ready(event_map.get(&id).cloned());
|
let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned());
|
||||||
let exists = |id: <PduEvent as Event>::Id| ready(event_map.get(&id).is_some());
|
let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some());
|
||||||
let resolved = super::resolve(
|
let resolved =
|
||||||
&RoomVersionId::V6,
|
super::resolve(&RoomVersionId::V6, state_sets, &auth_chain_sets, &fetch, &exists)
|
||||||
state_sets,
|
.await;
|
||||||
&auth_chain_sets,
|
|
||||||
&fetch,
|
|
||||||
&exists,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match resolved {
|
match resolved {
|
||||||
| Ok(state) => state,
|
| Ok(state) => state,
|
||||||
@@ -247,8 +241,8 @@ impl<E: Event + Clone> TestStore<E> {
|
|||||||
pub(crate) fn auth_event_ids(
|
pub(crate) fn auth_event_ids(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
event_ids: Vec<E::Id>,
|
event_ids: Vec<OwnedEventId>,
|
||||||
) -> Result<HashSet<E::Id>> {
|
) -> Result<HashSet<OwnedEventId>> {
|
||||||
let mut result = HashSet::new();
|
let mut result = HashSet::new();
|
||||||
let mut stack = event_ids;
|
let mut stack = event_ids;
|
||||||
|
|
||||||
@@ -584,7 +578,7 @@ pub(crate) fn INITIAL_EDGES() -> Vec<OwnedEventId> {
|
|||||||
|
|
||||||
pub(crate) mod event {
|
pub(crate) mod event {
|
||||||
use ruma::{
|
use ruma::{
|
||||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
||||||
events::{TimelineEventType, pdu::Pdu},
|
events::{TimelineEventType, pdu::Pdu},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -593,9 +587,7 @@ pub(crate) mod event {
|
|||||||
use crate::Event;
|
use crate::Event;
|
||||||
|
|
||||||
impl Event for PduEvent {
|
impl Event for PduEvent {
|
||||||
type Id = OwnedEventId;
|
fn event_id(&self) -> &EventId { &self.event_id }
|
||||||
|
|
||||||
fn event_id(&self) -> &Self::Id { &self.event_id }
|
|
||||||
|
|
||||||
fn room_id(&self) -> &RoomId {
|
fn room_id(&self) -> &RoomId {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
@@ -652,29 +644,31 @@ pub(crate) mod event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
|
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => Box::new(ev.prev_events.iter().map(|(id, _)| id)),
|
| Pdu::RoomV1Pdu(ev) =>
|
||||||
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter()),
|
Box::new(ev.prev_events.iter().map(|(id, _)| id.as_ref())),
|
||||||
|
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter().map(AsRef::as_ref)),
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + Send + '_> {
|
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => Box::new(ev.auth_events.iter().map(|(id, _)| id)),
|
| Pdu::RoomV1Pdu(ev) =>
|
||||||
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter()),
|
Box::new(ev.auth_events.iter().map(|(id, _)| id.as_ref())),
|
||||||
|
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter().map(AsRef::as_ref)),
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redacts(&self) -> Option<&Self::Id> {
|
fn redacts(&self) -> Option<&EventId> {
|
||||||
match &self.rest {
|
match &self.rest {
|
||||||
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
|
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_deref(),
|
||||||
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
|
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_deref(),
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
| _ => unreachable!("new PDU version"),
|
| _ => unreachable!("new PDU version"),
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ pub use ::toml;
|
|||||||
pub use ::tracing;
|
pub use ::tracing;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use info::{rustc_flags_capture, version, version::version};
|
pub use info::{
|
||||||
|
rustc_flags_capture, version,
|
||||||
|
version::{name, version},
|
||||||
|
};
|
||||||
pub use matrix::{Event, EventTypeExt, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res};
|
pub use matrix::{Event, EventTypeExt, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res};
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use utils::{ctor, dtor, implement, result, result::Result};
|
pub use utils::{ctor, dtor, implement, result, result::Result};
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ use conduwuit_core::{
|
|||||||
#[clap(
|
#[clap(
|
||||||
about,
|
about,
|
||||||
long_about = None,
|
long_about = None,
|
||||||
name = "conduwuit",
|
name = conduwuit_core::name(),
|
||||||
version = conduwuit_core::version(),
|
version = conduwuit_core::version(),
|
||||||
)]
|
)]
|
||||||
pub(crate) struct Args {
|
pub(crate) struct Args {
|
||||||
|
|||||||
+12
-7
@@ -98,12 +98,7 @@ pub(super) fn shutdown(server: &Arc<Server>, runtime: tokio::runtime::Runtime) {
|
|||||||
Level::INFO
|
Level::INFO
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(
|
wait_shutdown(server, runtime);
|
||||||
timeout = ?SHUTDOWN_TIMEOUT,
|
|
||||||
"Waiting for runtime..."
|
|
||||||
);
|
|
||||||
|
|
||||||
runtime.shutdown_timeout(SHUTDOWN_TIMEOUT);
|
|
||||||
let runtime_metrics = server.server.metrics.runtime_interval().unwrap_or_default();
|
let runtime_metrics = server.server.metrics.runtime_interval().unwrap_or_default();
|
||||||
|
|
||||||
event!(LEVEL, ?runtime_metrics, "Final runtime metrics");
|
event!(LEVEL, ?runtime_metrics, "Final runtime metrics");
|
||||||
@@ -111,13 +106,23 @@ pub(super) fn shutdown(server: &Arc<Server>, runtime: tokio::runtime::Runtime) {
|
|||||||
|
|
||||||
#[cfg(not(tokio_unstable))]
|
#[cfg(not(tokio_unstable))]
|
||||||
#[tracing::instrument(name = "stop", level = "info", skip_all)]
|
#[tracing::instrument(name = "stop", level = "info", skip_all)]
|
||||||
pub(super) fn shutdown(_server: &Arc<Server>, runtime: tokio::runtime::Runtime) {
|
pub(super) fn shutdown(server: &Arc<Server>, runtime: tokio::runtime::Runtime) {
|
||||||
|
wait_shutdown(server, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_shutdown(_server: &Arc<Server>, runtime: tokio::runtime::Runtime) {
|
||||||
debug!(
|
debug!(
|
||||||
timeout = ?SHUTDOWN_TIMEOUT,
|
timeout = ?SHUTDOWN_TIMEOUT,
|
||||||
"Waiting for runtime..."
|
"Waiting for runtime..."
|
||||||
);
|
);
|
||||||
|
|
||||||
runtime.shutdown_timeout(SHUTDOWN_TIMEOUT);
|
runtime.shutdown_timeout(SHUTDOWN_TIMEOUT);
|
||||||
|
|
||||||
|
// Join any jemalloc threads so they don't appear in use at exit.
|
||||||
|
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
|
||||||
|
conduwuit_core::alloc::je::background_thread_enable(false)
|
||||||
|
.log_debug_err()
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use std::{sync::Arc, time::Duration};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use conduwuit::{Result, Server, debug, info, warn};
|
use conduwuit::{Result, Server, debug, info, warn};
|
||||||
use database::{Deserialized, Map};
|
use database::{Deserialized, Map};
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::{Mentions, room::message::RoomMessageEventContent};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::Notify,
|
sync::Notify,
|
||||||
@@ -53,6 +53,8 @@ struct CheckForAnnouncementsResponseEntry {
|
|||||||
id: u64,
|
id: u64,
|
||||||
date: Option<String>,
|
date: Option<String>,
|
||||||
message: String,
|
message: String,
|
||||||
|
#[serde(default, skip_serializing_if = "bool::not")]
|
||||||
|
mention_room: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHECK_FOR_ANNOUNCEMENTS_URL: &str =
|
const CHECK_FOR_ANNOUNCEMENTS_URL: &str =
|
||||||
@@ -139,19 +141,20 @@ impl Service {
|
|||||||
} else {
|
} else {
|
||||||
info!("[announcements] {:#}", announcement.message);
|
info!("[announcements] {:#}", announcement.message);
|
||||||
}
|
}
|
||||||
|
let mut message = RoomMessageEventContent::text_markdown(format!(
|
||||||
|
"### New announcement{}\n\n{}",
|
||||||
|
announcement
|
||||||
|
.date
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(String::new, |date| format!(" - `{date}`")),
|
||||||
|
announcement.message
|
||||||
|
));
|
||||||
|
|
||||||
self.services
|
if announcement.mention_room {
|
||||||
.admin
|
message = message.add_mentions(Mentions::with_room_mention());
|
||||||
.send_message(RoomMessageEventContent::text_markdown(format!(
|
}
|
||||||
"### New announcement{}\n\n{}",
|
|
||||||
announcement
|
self.services.admin.send_message(message).await.ok();
|
||||||
.date
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(String::new, |date| format!(" - `{date}`")),
|
|
||||||
announcement.message
|
|
||||||
)))
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use conduwuit::{
|
|||||||
Error, Result, err, implement,
|
Error, Result, err, implement,
|
||||||
state_res::{self, StateMap},
|
state_res::{self, StateMap},
|
||||||
trace,
|
trace,
|
||||||
utils::stream::{IterStream, ReadyExt, TryWidebandExt, WidebandExt, automatic_width},
|
utils::stream::{IterStream, ReadyExt, TryWidebandExt, WidebandExt},
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join};
|
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join};
|
||||||
use ruma::{OwnedEventId, RoomId, RoomVersionId};
|
use ruma::{OwnedEventId, RoomId, RoomVersionId};
|
||||||
@@ -112,14 +112,7 @@ where
|
|||||||
{
|
{
|
||||||
let event_fetch = |event_id| self.event_fetch(event_id);
|
let event_fetch = |event_id| self.event_fetch(event_id);
|
||||||
let event_exists = |event_id| self.event_exists(event_id);
|
let event_exists = |event_id| self.event_exists(event_id);
|
||||||
state_res::resolve(
|
state_res::resolve(room_version, state_sets, auth_chain_sets, &event_fetch, &event_exists)
|
||||||
room_version,
|
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
|
||||||
state_sets,
|
.await
|
||||||
auth_chain_sets,
|
|
||||||
&event_fetch,
|
|
||||||
&event_exists,
|
|
||||||
automatic_width(),
|
|
||||||
)
|
|
||||||
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, VERSION_EXTRA};
|
use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag};
|
||||||
use conduwuit_service::state;
|
use conduwuit_service::state;
|
||||||
|
|
||||||
pub fn build() -> Router<state::State> {
|
pub fn build() -> Router<state::State> {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{%~ block footer ~%}
|
{%~ block footer ~%}
|
||||||
<footer>
|
<footer>
|
||||||
<p>Powered by <a href="https://continuwuity.org">Continuwuity</a>
|
<p>Powered by <a href="https://continuwuity.org">Continuwuity</a>
|
||||||
{%~ if let Some(version_info) = VERSION_EXTRA ~%}
|
{%~ if let Some(version_info) = self::version_tag() ~%}
|
||||||
{%~ if let Some(url) = GIT_REMOTE_COMMIT_URL.or(GIT_REMOTE_WEB_URL) ~%}
|
{%~ if let Some(url) = GIT_REMOTE_COMMIT_URL.or(GIT_REMOTE_WEB_URL) ~%}
|
||||||
(<a href="{{ url }}">{{ version_info }}</a>)
|
(<a href="{{ url }}">{{ version_info }}</a>)
|
||||||
{%~ else ~%}
|
{%~ else ~%}
|
||||||
|
|||||||
Reference in New Issue
Block a user