mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-18 19:25:43 +00:00
Compare commits
53 Commits
v0.5.5
...
jade/snafu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a8c409ff9 | ||
|
|
688ef727e5 | ||
|
|
3de026160e | ||
|
|
9fe761513d | ||
|
|
abf1e1195a | ||
|
|
d9537e9b55 | ||
|
|
0d1de70d8f | ||
|
|
4aa03a71eb | ||
|
|
f847918575 | ||
|
|
7569a0545b | ||
|
|
b6c5991e1f | ||
|
|
efd879fcd8 | ||
|
|
92a848f74d | ||
|
|
776b5865ba | ||
|
|
722bacbe89 | ||
|
|
46907e3dce | ||
|
|
31e2195e56 | ||
|
|
7ecac93ddc | ||
|
|
6a0b103722 | ||
|
|
23d77b614f | ||
|
|
e01aa44b16 | ||
|
|
a08739c246 | ||
|
|
c14864b881 | ||
|
|
1773e72e68 | ||
|
|
0f94d55689 | ||
|
|
abfb6377c2 | ||
|
|
91d64f5b24 | ||
|
|
9a3f3f6e78 | ||
|
|
b3e31a4aad | ||
|
|
8cda431cc6 | ||
|
|
02b9a3f713 | ||
|
|
d40893730c | ||
|
|
28fae58cf6 | ||
|
|
f458f6ab76 | ||
|
|
fdf9cea533 | ||
|
|
ecb1b73c84 | ||
|
|
e03082480a | ||
|
|
f9e7f019ad | ||
|
|
12069e7c86 | ||
|
|
77928a62b4 | ||
|
|
c73cb5c1bf | ||
|
|
a140eacb04 | ||
|
|
40536b13da | ||
|
|
cacd8681d1 | ||
|
|
b095518e6f | ||
|
|
a91add4aca | ||
|
|
7fec48423a | ||
|
|
2f6b7c7a40 | ||
|
|
48ab6adec1 | ||
|
|
592244d5aa | ||
|
|
091893f8bc | ||
|
|
6eba6a838e | ||
|
|
1a11c784f5 |
@@ -23,7 +23,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.43.4
|
||||
rev: v1.43.5
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
|
||||
@@ -24,3 +24,5 @@ extend-ignore-re = [
|
||||
"continuwity" = "continuwuity"
|
||||
"execuse" = "execuse"
|
||||
"oltp" = "OTLP"
|
||||
|
||||
rememvering = "remembering"
|
||||
|
||||
@@ -85,24 +85,31 @@ ### Matrix tests
|
||||
|
||||
### Writing documentation
|
||||
|
||||
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages
|
||||
Continuwuity's website uses [`rspress`][rspress] and is deployed via CI using Cloudflare Pages
|
||||
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
|
||||
directory at the top level.
|
||||
|
||||
To build the documentation locally:
|
||||
To load the documentation locally:
|
||||
|
||||
1. Install NodeJS and npm from their [official website][nodejs-download] or via your package manager of choice
|
||||
|
||||
2. From the project's root directory, install the relevant npm modules
|
||||
|
||||
1. Install mdbook if you don't have it already:
|
||||
```bash
|
||||
cargo install mdbook # or cargo binstall, or another method
|
||||
npm ci
|
||||
```
|
||||
|
||||
2. Build the documentation:
|
||||
3. Make changes to the document pages as you see fit
|
||||
|
||||
4. Generate a live preview of the documentation
|
||||
|
||||
```bash
|
||||
mdbook build
|
||||
npm run docs:dev
|
||||
```
|
||||
|
||||
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
|
||||
A webserver for the docs will be spun up for you (e.g. at `http://localhost:3000`). Any changes you make to the documentation will be live-reloaded on the webpage.
|
||||
|
||||
Alternatively, you can build the documentation using `npm run docs:build` - the output of this will be in the `/doc_build` directory. Once you're happy with your documentation updates, you can commit the changes.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
@@ -169,5 +176,6 @@ ### Creating pull requests
|
||||
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
|
||||
[complement]: https://github.com/matrix-org/complement/
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[nodejs-download]: https://nodejs.org/en/download
|
||||
[rspress]: https://rspress.rs/
|
||||
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
||||
|
||||
1077
Cargo.lock
generated
1077
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -68,7 +68,7 @@ default-features = false
|
||||
version = "0.1.3"
|
||||
|
||||
[workspace.dependencies.rand]
|
||||
version = "0.8.5"
|
||||
version = "0.10.0"
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
[workspace.dependencies.bytes]
|
||||
@@ -84,7 +84,7 @@ version = "1.3.1"
|
||||
version = "1.11.1"
|
||||
|
||||
[workspace.dependencies.axum]
|
||||
version = "0.7.9"
|
||||
version = "0.8.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"form",
|
||||
@@ -97,7 +97,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.axum-extra]
|
||||
version = "0.9.6"
|
||||
version = "0.10.1"
|
||||
default-features = false
|
||||
features = ["typed-header", "tracing"]
|
||||
|
||||
@@ -110,7 +110,7 @@ default-features = false
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.axum-client-ip]
|
||||
version = "0.6.1"
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.tower]
|
||||
version = "0.5.2"
|
||||
@@ -118,7 +118,7 @@ default-features = false
|
||||
features = ["util"]
|
||||
|
||||
[workspace.dependencies.tower-http]
|
||||
version = "0.6.2"
|
||||
version = "0.6.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"add-extension",
|
||||
@@ -253,7 +253,7 @@ features = [
|
||||
version = "0.4.0"
|
||||
|
||||
[workspace.dependencies.libloading]
|
||||
version = "0.8.6"
|
||||
version = "0.9.0"
|
||||
|
||||
# Validating urls in config, was already a transitive dependency
|
||||
[workspace.dependencies.url]
|
||||
@@ -298,7 +298,7 @@ default-features = false
|
||||
features = ["env", "toml"]
|
||||
|
||||
[workspace.dependencies.hickory-resolver]
|
||||
version = "0.25.1"
|
||||
version = "0.25.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"serde",
|
||||
@@ -307,9 +307,14 @@ features = [
|
||||
]
|
||||
|
||||
# Used for conduwuit::Error type
|
||||
[workspace.dependencies.thiserror]
|
||||
version = "2.0.12"
|
||||
[workspace.dependencies.snafu]
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
features = ["std", "rust_1_81"]
|
||||
|
||||
# Used for macro name generation
|
||||
[workspace.dependencies.paste]
|
||||
version = "1.0"
|
||||
|
||||
# Used when hashing the state
|
||||
[workspace.dependencies.ring]
|
||||
@@ -342,7 +347,8 @@ version = "0.1.2"
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "b496b7f38d517149361a882e75d3fd4faf210441"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "e087ff15888156942ca2ffe6097d1b4c3fd27628"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -424,7 +430,7 @@ features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
|
||||
|
||||
# optional sentry metrics for crash/panic reporting
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -440,9 +446,9 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sentry-tracing]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
[workspace.dependencies.sentry-tower]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
|
||||
# jemalloc usage
|
||||
[workspace.dependencies.tikv-jemalloc-sys]
|
||||
@@ -471,7 +477,7 @@ features = ["use_std"]
|
||||
version = "0.5"
|
||||
|
||||
[workspace.dependencies.nix]
|
||||
version = "0.30.1"
|
||||
version = "0.31.0"
|
||||
default-features = false
|
||||
features = ["resource"]
|
||||
|
||||
@@ -553,7 +559,7 @@ version = "0.7.5"
|
||||
version = "1.0.1"
|
||||
|
||||
[workspace.dependencies.askama]
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
|
||||
#
|
||||
# Patches
|
||||
|
||||
1
changelog.d/1393.bugfix
Normal file
1
changelog.d/1393.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex
|
||||
1
changelog.d/1399.feature
Normal file
1
changelog.d/1399.feature
Normal file
@@ -0,0 +1 @@
|
||||
Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex.
|
||||
1
changelog.d/1418.bugfix
Normal file
1
changelog.d/1418.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160.
|
||||
1
changelog.d/1421.bugfix
Normal file
1
changelog.d/1421.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers' config option is set correctly. Contributed by @katie.
|
||||
1
changelog.d/list-backups-formatting.feature
Normal file
1
changelog.d/list-backups-formatting.feature
Normal file
@@ -0,0 +1 @@
|
||||
Updated `list-backups` admin command to output one backup per line.
|
||||
@@ -1056,14 +1056,6 @@
|
||||
#
|
||||
#rocksdb_repair = false
|
||||
|
||||
# This item is undocumented. Please contribute documentation for it.
|
||||
#
|
||||
#rocksdb_read_only = false
|
||||
|
||||
# This item is undocumented. Please contribute documentation for it.
|
||||
#
|
||||
#rocksdb_secondary = false
|
||||
|
||||
# Enables idle CPU priority for compaction thread. This is not enabled by
|
||||
# default to prevent compaction from falling too far behind on busy
|
||||
# systems.
|
||||
@@ -1120,27 +1112,34 @@
|
||||
|
||||
# Allow local (your server only) presence updates/requests.
|
||||
#
|
||||
# Note that presence on continuwuity is very fast unlike Synapse's. If
|
||||
# using outgoing presence, this MUST be enabled.
|
||||
# Local presence must be enabled for outgoing presence to function.
|
||||
#
|
||||
# Note that local presence is not as heavy on the CPU as federated
|
||||
# presence, but will still become more expensive the more local users you
|
||||
# have.
|
||||
#
|
||||
#allow_local_presence = true
|
||||
|
||||
# Allow incoming federated presence updates/requests.
|
||||
# Allow incoming federated presence updates.
|
||||
#
|
||||
# This option receives presence updates from other servers, but does not
|
||||
# send any unless `allow_outgoing_presence` is true. Note that presence on
|
||||
# continuwuity is very fast unlike Synapse's.
|
||||
# This option enables processing inbound presence updates from other
|
||||
# servers. Without it, remote users will appear as if they are always
|
||||
# offline to your local users. This does not affect typing indicators or
|
||||
# read receipts.
|
||||
#
|
||||
#allow_incoming_presence = true
|
||||
|
||||
# Allow outgoing presence updates/requests.
|
||||
#
|
||||
# This option sends presence updates to other servers, but does not
|
||||
# receive any unless `allow_incoming_presence` is true. Note that presence
|
||||
# on continuwuity is very fast unlike Synapse's. If using outgoing
|
||||
# presence, you MUST enable `allow_local_presence` as well.
|
||||
# This option sends presence updates to other servers, and requires that
|
||||
# `allow_local_presence` is also enabled.
|
||||
#
|
||||
#allow_outgoing_presence = true
|
||||
# Note that outgoing presence is very heavy on the CPU and network, and
|
||||
# will typically cause extreme strain and slowdowns for no real benefit.
|
||||
# There are only a few clients that even implement presence, so you
|
||||
# probably don't want to enable this.
|
||||
#
|
||||
#allow_outgoing_presence = false
|
||||
|
||||
# How many seconds without presence updates before you become idle.
|
||||
# Defaults to 5 minutes.
|
||||
@@ -1174,6 +1173,10 @@
|
||||
|
||||
# Allow sending read receipts to remote servers.
|
||||
#
|
||||
# Note that sending read receipts to remote servers in large rooms with
|
||||
# lots of other homeservers may cause additional strain on the CPU and
|
||||
# network.
|
||||
#
|
||||
#allow_outgoing_read_receipts = true
|
||||
|
||||
# Allow local typing updates.
|
||||
@@ -1185,6 +1188,10 @@
|
||||
|
||||
# Allow outgoing typing updates to federation.
|
||||
#
|
||||
# Note that sending typing indicators to remote servers in large rooms
|
||||
# with lots of other homeservers may cause additional strain on the CPU
|
||||
# and network.
|
||||
#
|
||||
#allow_outgoing_typing = true
|
||||
|
||||
# Allow incoming typing updates from federation.
|
||||
@@ -1318,7 +1325,7 @@
|
||||
# sender user's server name, inbound federation X-Matrix origin, and
|
||||
# outbound federation handler.
|
||||
#
|
||||
# You can set this to ["*"] to block all servers by default, and then
|
||||
# You can set this to [".*"] to block all servers by default, and then
|
||||
# use `allowed_remote_server_names` to allow only specific servers.
|
||||
#
|
||||
# example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
||||
|
||||
@@ -52,7 +52,7 @@ ENV BINSTALL_VERSION=1.17.5
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
ENV LDDTREE_VERSION=0.5.0
|
||||
# renovate: datasource=crate depName=timelord-cli
|
||||
ENV TIMELORD_VERSION=3.0.1
|
||||
|
||||
@@ -162,6 +162,7 @@ ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA
|
||||
ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA
|
||||
|
||||
ARG RUST_PROFILE=release
|
||||
ARG CARGO_FEATURES="default,http3"
|
||||
|
||||
# Build the binary
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
@@ -171,11 +172,20 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
set -o allexport
|
||||
set -o xtrace
|
||||
. /etc/environment
|
||||
|
||||
# Check if http3 feature is enabled and set appropriate RUSTFLAGS
|
||||
if echo "${CARGO_FEATURES}" | grep -q "http3"; then
|
||||
export RUSTFLAGS="${RUSTFLAGS} --cfg reqwest_unstable"
|
||||
else
|
||||
export RUSTFLAGS="${RUSTFLAGS}"
|
||||
fi
|
||||
|
||||
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
|
||||
jq -r ".target_directory"))
|
||||
mkdir /out/sbin
|
||||
PACKAGE=conduwuit
|
||||
xx-cargo build --locked --profile ${RUST_PROFILE} \
|
||||
--no-default-features --features ${CARGO_FEATURES} \
|
||||
-p $PACKAGE;
|
||||
BINARIES=($(cargo metadata --no-deps --format-version 1 | \
|
||||
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
|
||||
|
||||
@@ -22,7 +22,7 @@ ENV BINSTALL_VERSION=1.17.5
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
ENV LDDTREE_VERSION=0.5.0
|
||||
|
||||
# Install unpackaged tools
|
||||
RUN <<EOF
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"label": "Deploying"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"name": "turn",
|
||||
"label": "TURN"
|
||||
"type": "dir",
|
||||
"name": "calls",
|
||||
"label": "Calls"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"text": "Guide",
|
||||
"link": "/introduction",
|
||||
"activeMatch": "^/(introduction|configuration|deploying|turn|appservices|maintenance|troubleshooting)"
|
||||
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting)"
|
||||
},
|
||||
{
|
||||
"text": "Development",
|
||||
|
||||
13
docs/calls.mdx
Normal file
13
docs/calls.mdx
Normal file
@@ -0,0 +1,13 @@
|
||||
# Calls
|
||||
|
||||
Matrix supports two types of calls:
|
||||
|
||||
- Element Call powered by [MatrixRTC](https://half-shot.github.io/msc-crafter/#msc/4143) and [LiveKit](https://github.com/livekit/livekit)
|
||||
- Legacy calls, sometimes using Jitsi
|
||||
|
||||
Both types of calls are supported by different sets of clients, but most clients are moving towards MatrixRTC / Element Call.
|
||||
|
||||
For either one to work correctly, you have to do some additional setup.
|
||||
|
||||
- For legacy calls to work, you need to set up a TURN/STUN server. [Read the TURN guide for tips on how to set up coturn](./calls/turn.mdx)
|
||||
- For MatrixRTC / Element Call to work, you have to set up the LiveKit backend (foci). LiveKit also uses TURN/STUN to increase reliability, so you might want to configure your TURN server first. [Read the LiveKit guide](./calls/livekit.mdx)
|
||||
12
docs/calls/_meta.json
Normal file
12
docs/calls/_meta.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"type": "file",
|
||||
"name": "turn",
|
||||
"label": "TURN"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"name": "livekit",
|
||||
"label": "MatrixRTC / LiveKit"
|
||||
}
|
||||
]
|
||||
268
docs/calls/livekit.mdx
Normal file
268
docs/calls/livekit.mdx
Normal file
@@ -0,0 +1,268 @@
|
||||
# Matrix RTC/Element Call Setup
|
||||
|
||||
:::info
|
||||
This guide assumes that you are using docker compose for deployment. LiveKit only provides Docker images.
|
||||
:::
|
||||
|
||||
## Instructions
|
||||
|
||||
### 1. Domain
|
||||
|
||||
LiveKit should live on its own domain or subdomain. In this guide we use `livekit.example.com` - this should be replaced with a domain you control.
|
||||
|
||||
Make sure the DNS record for the (sub)domain you plan to use is pointed to your server.
|
||||
|
||||
### 2. Services
|
||||
|
||||
Using LiveKit with Matrix requires two services - Livekit itself, and a service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
|
||||
|
||||
You must generate a key and secret to allow the Matrix service to authenticate with LiveKit. `LK_MATRIX_KEY` should be around 20 random characters, and `LK_MATRIX_SECRET` should be around 64. Remember to replace these with the actual values!
|
||||
|
||||
:::tip Generating the secrets
|
||||
LiveKit provides a utility to generate secure random keys
|
||||
```bash
|
||||
docker run --rm livekit/livekit-server:latest generate-keys
|
||||
```
|
||||
:::
|
||||
|
||||
```yaml
|
||||
services:
|
||||
lk-jwt-service:
|
||||
image: ghcr.io/element-hq/lk-jwt-service:latest
|
||||
container_name: lk-jwt-service
|
||||
environment:
|
||||
- LIVEKIT_JWT_BIND=:8081
|
||||
- LIVEKIT_URL=wss://livekit.example.com
|
||||
- LIVEKIT_KEY=LK_MATRIX_KEY
|
||||
- LIVEKIT_SECRET=LK_MATRIX_SECRET
|
||||
- LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:latest
|
||||
container_name: livekit
|
||||
command: --config /etc/livekit.yaml
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./livekit.yaml:/etc/livekit.yaml:ro
|
||||
network_mode: "host" # /!\ LiveKit binds to all addresses by default.
|
||||
# Make sure port 7880 is blocked by your firewall to prevent access bypassing your reverse proxy
|
||||
# Alternatively, uncomment the lines below and comment `network_mode: "host"` above to specify port mappings.
|
||||
# ports:
|
||||
# - "127.0.0.1:7880:7880/tcp"
|
||||
# - "7881:7881/tcp"
|
||||
# - "50100-50200:50100-50200/udp"
|
||||
```
|
||||
|
||||
Next, we need to configure LiveKit. In the same directory, create `livekit.yaml` with the following content - remembering to replace `LK_MATRIX_KEY` and `LK_MATRIX_SECRET` with the values you generated:
|
||||
|
||||
```yaml
|
||||
port: 7880
|
||||
bind_addresses:
|
||||
- ""
|
||||
rtc:
|
||||
tcp_port: 7881
|
||||
port_range_start: 50100
|
||||
port_range_end: 50200
|
||||
use_external_ip: true
|
||||
enable_loopback_candidate: false
|
||||
keys:
|
||||
LK_MATRIX_KEY: LK_MATRIX_SECRET
|
||||
```
|
||||
|
||||
#### Firewall hints
|
||||
|
||||
You will need to allow ports `7881/tcp` and `50100:50200/udp` through your firewall. If you use UFW, the commands are: `ufw allow 7881/tcp` and `ufw allow 50100:50200/udp`.
|
||||
|
||||
### 3. Telling clients where to find LiveKit
|
||||
|
||||
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to your client .well-known file. To do so, in the config section `global.well-known`, add (or modify) the option `rtc_focus_server_urls`.
|
||||
|
||||
The variable should be a list of servers serving as MatrixRTC endpoints to serve in the well-known file to the client.
|
||||
|
||||
```toml
|
||||
rtc_focus_server_urls = [
|
||||
{ type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||
]
|
||||
```
|
||||
|
||||
Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to.
|
||||
|
||||
#### Serving .well-known manually
|
||||
|
||||
If you don't let Continuwuity serve your `.well-known` files, you need to add the following lines to your `.well-known/matrix/client` file, remembering to replace the URL with your own `lk-jwt-service` deployment:
|
||||
|
||||
```json
|
||||
"org.matrix.msc4143.rtc_foci": [
|
||||
{
|
||||
"type": "livekit",
|
||||
"livekit_service_url": "https://livekit.example.com"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The final file should look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url":"https://matrix.example.com"
|
||||
},
|
||||
"org.matrix.msc4143.rtc_foci": [
|
||||
{
|
||||
"type": "livekit",
|
||||
"livekit_service_url": "https://livekit.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Configure your Reverse Proxy
|
||||
|
||||
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
|
||||
|
||||
By default, all routes should be forwarded to Livekit with the exception of the following path prefixes, which should be forwarded to the JWT/Authentication service:
|
||||
|
||||
- `/sfu/get`
|
||||
- `/healthz`
|
||||
- `/get_token`
|
||||
|
||||
<details>
|
||||
<summary>Example caddy config</summary>
|
||||
```
|
||||
matrix-rtc.example.com {
|
||||
|
||||
# for lk-jwt-service
|
||||
@lk-jwt-service path /sfu/get* /healthz* /get_token*
|
||||
route @lk-jwt-service {
|
||||
reverse_proxy 127.0.0.1:8081
|
||||
}
|
||||
|
||||
# for livekit
|
||||
reverse_proxy 127.0.0.1:7880
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example nginx config</summary>
|
||||
```
|
||||
server {
|
||||
server_name matrix-rtc.example.com;
|
||||
|
||||
# for lk-jwt-service
|
||||
location ~ ^/(sfu/get|healthz|get_token) {
|
||||
proxy_pass http://127.0.0.1:8081$request_uri;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# for livekit
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:7880$request_uri;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_buffering off;
|
||||
|
||||
# websocket
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that for websockets to work, you need to have this somewhere outside your server block:
|
||||
```
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example traefik router</summary>
|
||||
```
|
||||
# on LiveKit itself
|
||||
traefik.http.routers.livekit.rule=Host(`livekit.example.com`)
|
||||
# on the JWT service
|
||||
traefik.http.routers.livekit-jwt.rule=Host(`livekit.example.com`) && (PathPrefix(`/sfu/get`) || PathPrefix(`/healthz`) || PathPrefix(`/get_token`))
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
### 6. Start Everything
|
||||
|
||||
Start up the services using your usual method - for example `docker compose up -d`.
|
||||
|
||||
## Additional Configuration
|
||||
|
||||
### TURN Integration
|
||||
|
||||
If you've already set up coturn, there may be a port clash between the two services. To fix this, make sure the `min-port` and `max-port` for coturn so it doesn't overlap with LiveKit's range:
|
||||
|
||||
```ini
|
||||
min-port=50201
|
||||
max-port=65535
|
||||
```
|
||||
|
||||
To improve LiveKit's reliability, you can configure it to use your coturn server.
|
||||
|
||||
Generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want - so set a different one for each thing using your TURN server.
|
||||
|
||||
Then configure livekit, making sure to replace `COTURN_SECRET`:
|
||||
|
||||
```yaml
|
||||
# livekit.yaml
|
||||
rtc:
|
||||
turn_servers:
|
||||
- host: coturn.ellis.link
|
||||
port: 3478
|
||||
protocol: tcp
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.ellis.link
|
||||
port: 5349
|
||||
protocol: tls # Only if you've set up TLS in your coturn
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.ellis.link
|
||||
port: 3478
|
||||
protocol: udp
|
||||
secret: "COTURN_SECRET"
|
||||
```
|
||||
|
||||
## LiveKit's built in TURN server
|
||||
|
||||
Livekit includes a built in TURN server which can be used in place of an external option. This TURN server will only work with Livekit, so you can't use it for legacy Matrix calling - or anything else.
|
||||
|
||||
If you don't want to set up a separate TURN server, you can enable this with the following changes:
|
||||
|
||||
```yaml
|
||||
### add this to livekit.yaml ###
|
||||
turn:
|
||||
enabled: true
|
||||
udp_port: 3478
|
||||
relay_range_start: 50300
|
||||
relay_range_end: 50400
|
||||
domain: matrix-rtc.example.com
|
||||
```
|
||||
|
||||
```yaml
|
||||
### Add these to docker-compose ###
|
||||
- "3478:3478/udp"
|
||||
- "50300-50400:50300-50400/udp"
|
||||
```
|
||||
|
||||
### Related Documentation
|
||||
|
||||
- [LiveKit GitHub](https://github.com/livekit/livekit)
|
||||
- [LiveKit Connection Tester](https://livekit.io/connection-test) - use with the token returned by `/sfu/get` or `/get_token`
|
||||
- [MatrixRTC proposal](https://half-shot.github.io/msc-crafter/#msc/4143)
|
||||
- [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
|
||||
- [Community guide](https://tomfos.tr/matrix/livekit/)
|
||||
- [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
|
||||
214
docs/calls/turn.mdx
Normal file
214
docs/calls/turn.mdx
Normal file
@@ -0,0 +1,214 @@
|
||||
# Setting up TURN/STUN
|
||||
|
||||
[TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and [STUN](https://en.wikipedia.org/wiki/STUN) are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.
|
||||
|
||||
Continuwuity recommends using [Coturn](https://github.com/coturn/coturn) as your TURN/STUN server, which is available as a Docker image or a distro package.
|
||||
|
||||
## Installing Coturn
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```ini
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
|
||||
:::tip Generating a secure secret
|
||||
A common way to generate a suitable alphanumeric secret key is by using:
|
||||
```bash
|
||||
pwgen -s 64 1
|
||||
```
|
||||
:::
|
||||
|
||||
#### Port Configuration
|
||||
|
||||
By default, coturn uses the following ports:
|
||||
- `3478` (UDP/TCP): Standard TURN/STUN port
|
||||
- `5349` (UDP/TCP): TURN/STUN over TLS
|
||||
- `49152-65535` (UDP): Media relay ports
|
||||
|
||||
If you're also running LiveKit, you'll need to avoid port conflicts. Configure non-overlapping port ranges:
|
||||
|
||||
```ini
|
||||
# In coturn.conf
|
||||
min-port=50201
|
||||
max-port=65535
|
||||
```
|
||||
|
||||
This leaves ports `50100-50200` available for LiveKit's default configuration.
|
||||
|
||||
### Running with Docker
|
||||
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using:
|
||||
|
||||
```bash
|
||||
docker run -d --network=host \
|
||||
-v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf \
|
||||
coturn/coturn
|
||||
```
|
||||
|
||||
### Running with Docker Compose
|
||||
|
||||
Create a `docker-compose.yml` file and run `docker compose up -d`:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
:::info Why host networking?
|
||||
Coturn uses host networking mode because it needs to bind to multiple ports and work with various network protocols. Using host networking is better for performance, and reduces configuration complexity. To understand alternative configuration options, visit [Coturn's Docker documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
|
||||
:::
|
||||
|
||||
### Security Recommendations
|
||||
|
||||
For security best practices, see Synapse's [Coturn documentation](https://element-hq.github.io/synapse/latest/turn-howto.html), which includes important firewall and access control recommendations.
|
||||
|
||||
## Configuring Continuwuity
|
||||
|
||||
Once your TURN server is running, configure Continuwuity to provide credentials to clients. Add the following to your Continuwuity configuration file:
|
||||
|
||||
### Shared Secret Authentication (Recommended)
|
||||
|
||||
This is the most secure method and generates time-limited credentials automatically:
|
||||
|
||||
```toml
|
||||
# TURN URIs that clients should connect to
|
||||
turn_uris = [
|
||||
"turn:coturn.example.com?transport=udp",
|
||||
"turn:coturn.example.com?transport=tcp",
|
||||
"turns:coturn.example.com?transport=udp",
|
||||
"turns:coturn.example.com?transport=tcp"
|
||||
]
|
||||
|
||||
# Shared secret for generating credentials (must match coturn's static-auth-secret)
|
||||
turn_secret = "<your coturn static-auth-secret>"
|
||||
|
||||
# Optional: Read secret from a file instead (takes priority over turn_secret)
|
||||
# turn_secret_file = "/etc/continuwuity/.turn_secret"
|
||||
|
||||
# TTL for generated credentials in seconds (default: 86400 = 24 hours)
|
||||
turn_ttl = 86400
|
||||
```
|
||||
|
||||
:::tip Using TLS
|
||||
The `turns:` URI prefix instructs clients to connect to TURN over TLS, which is highly recommended for security. Make sure you've configured TLS in your coturn server first.
|
||||
:::
|
||||
|
||||
### Static Credentials (Alternative)
|
||||
|
||||
If you prefer static username/password credentials instead of shared secrets:
|
||||
|
||||
```toml
|
||||
turn_uris = [
|
||||
"turn:coturn.example.com?transport=udp",
|
||||
"turn:coturn.example.com?transport=tcp"
|
||||
]
|
||||
|
||||
turn_username = "your_username"
|
||||
turn_password = "your_password"
|
||||
```
|
||||
|
||||
:::warning
|
||||
Static credentials are less secure than shared secrets because they don't expire and must be configured in coturn separately. It is strongly advised you use shared secret authentication.
|
||||
:::
|
||||
|
||||
### Guest Access
|
||||
|
||||
By default, TURN credentials require client authentication. To allow unauthenticated access:
|
||||
|
||||
```toml
|
||||
turn_allow_guests = true
|
||||
```
|
||||
|
||||
:::caution
|
||||
This is not recommended as it allows unauthenticated users to access your TURN server, potentially enabling abuse by bots. All major Matrix clients that support legacy calls *also* support authenticated TURN access.
|
||||
:::
|
||||
|
||||
### Important Notes
|
||||
|
||||
- Replace `coturn.example.com` with your actual TURN server domain (the `realm` from coturn.conf)
|
||||
- The `turn_secret` must match the `static-auth-secret` in your coturn configuration
|
||||
- Restart or reload Continuwuity after making configuration changes
|
||||
|
||||
## Testing Your TURN Server
|
||||
|
||||
### Testing Credentials
|
||||
|
||||
Verify that Continuwuity is correctly serving TURN credentials to clients:
|
||||
|
||||
```bash
|
||||
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
|
||||
-H "Authorization: Bearer <your_client_token>" | jq
|
||||
```
|
||||
|
||||
You should receive a response like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "1752792167:@jade:example.com",
|
||||
"password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
|
||||
"uris": [
|
||||
"turns:coturn.example.com?transport=udp",
|
||||
"turns:coturn.example.com?transport=tcp",
|
||||
"turn:coturn.example.com?transport=udp",
|
||||
"turn:coturn.example.com?transport=tcp"
|
||||
],
|
||||
"ttl": 86400
|
||||
}
|
||||
```
|
||||
|
||||
:::note MSC4166 Compliance
|
||||
If no TURN URIs are configured (`turn_uris` is empty), Continuwuity will return a 404 Not Found response, as specified in MSC4166.
|
||||
:::
|
||||
|
||||
### Testing Connectivity
|
||||
|
||||
Use [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/) to verify that the TURN credentials actually work:
|
||||
|
||||
1. Copy the credentials from the response above
|
||||
2. Paste them into the Trickle ICE testing tool
|
||||
3. Click "Gather candidates"
|
||||
4. Look for successful `relay` candidates in the results
|
||||
|
||||
If you see relay candidates, your TURN server is working correctly!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Clients can't connect to TURN server
|
||||
|
||||
- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
|
||||
- Check that DNS resolves correctly for your TURN domain
|
||||
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
|
||||
- Test with Trickle ICE to isolate the issue
|
||||
|
||||
### Port conflicts with LiveKit
|
||||
|
||||
- Make sure coturn's `min-port` starts above LiveKit's `port_range_end` (default: 50200)
|
||||
- Or adjust LiveKit's port range to avoid coturn's default range
|
||||
|
||||
### 404 when calling turnServer endpoint
|
||||
|
||||
- Verify that `turn_uris` is not empty in your Continuwuity config
|
||||
- This behavior is correct per MSC4166 if no TURN URIs are configured
|
||||
|
||||
### Credentials expire too quickly
|
||||
|
||||
- Adjust the `turn_ttl` value in your Continuwuity configuration
|
||||
- Default is 86400 seconds (24 hours)
|
||||
|
||||
### Related Documentation
|
||||
|
||||
- [MatrixRTC/LiveKit Setup](./livekit.mdx) - Configure group calling with LiveKit
|
||||
- [Coturn GitHub](https://github.com/coturn/coturn) - Official coturn repository
|
||||
- [Synapse TURN Guide](https://element-hq.github.io/synapse/latest/turn-howto.html) - Additional security recommendations
|
||||
@@ -217,4 +217,4 @@ ### Use Traefik as Proxy
|
||||
|
||||
## Voice communication
|
||||
|
||||
See the [TURN](../turn.md) page.
|
||||
See the [Calls](../calls.mdx) page.
|
||||
|
||||
@@ -3,3 +3,5 @@ # Continuwuity for FreeBSD
|
||||
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
|
||||
|
||||
Contributions to get Continuwuity packaged for FreeBSD are welcome.
|
||||
|
||||
Please join our [Continuwuity BSD](https://matrix.to/#/%23bsd:continuwuity.org) community room.
|
||||
|
||||
@@ -56,6 +56,8 @@ ### Building with the Rust toolchain
|
||||
|
||||
You can build Continuwuity using `cargo build --release`.
|
||||
|
||||
Continuwuity supports various optional features that can be enabled during compilation. Please see the Cargo.toml file for a comprehensive list, or ask in our rooms.
|
||||
|
||||
### Building with Nix
|
||||
|
||||
If you prefer, you can use Nix (or [Lix](https://lix.systems)) to build Continuwuity. This provides improved reproducibility and makes it easy to set up a build environment and generate output. This approach also allows for easy cross-compilation.
|
||||
@@ -277,7 +279,7 @@ # What's next?
|
||||
|
||||
## Audio/Video calls
|
||||
|
||||
For Audio/Video call functionality see the [TURN Guide](../turn.md).
|
||||
For Audio/Video call functionality see the [Calls](../calls.md) page.
|
||||
|
||||
## Appservices
|
||||
|
||||
|
||||
@@ -1,7 +1,109 @@
|
||||
# Continuwuity for Kubernetes
|
||||
|
||||
Continuwuity doesn't support horizontal scalability or distributed loading
|
||||
natively. However, [a community-maintained Helm Chart is available here to run
|
||||
natively. However, a deployment in Kubernetes is very similar to the docker
|
||||
setup. This is because Continuwuity can be fully configured using environment
|
||||
variables. A sample StatefulSet is shared below. The only thing missing is
|
||||
a PVC definition (named `continuwuity-data`) for the volume mounted to
|
||||
the StatefulSet, an Ingress resources to point your webserver to the
|
||||
Continuwuity Pods, and a Service resource (targeting `app.kubernetes.io/name: continuwuity`)
|
||||
to glue the Ingress and Pod together.
|
||||
|
||||
Carefully go through the `env` section and add, change, and remove any env vars you like using the [Configuration reference](https://continuwuity.org/reference/config.html)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: continuwuity
|
||||
namespace: matrix
|
||||
labels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: continuwuity
|
||||
podManagementPolicy: Parallel
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
spec:
|
||||
securityContext:
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
containers:
|
||||
- name: continuwuity
|
||||
# use a sha hash <3
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- mountPath: /data
|
||||
name: data
|
||||
subPath: data
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
env:
|
||||
- name: TOKIO_WORKER_THREADS
|
||||
value: "2"
|
||||
- name: CONTINUWUITY_SERVER_NAME
|
||||
value: "example.com"
|
||||
- name: CONTINUWUITY_DATABASE_PATH
|
||||
value: "/data/db"
|
||||
- name: CONTINUWUITY_DATABASE_BACKEND
|
||||
value: "rocksdb"
|
||||
- name: CONTINUWUITY_PORT
|
||||
value: "80"
|
||||
- name: CONTINUWUITY_MAX_REQUEST_SIZE
|
||||
value: "20000000"
|
||||
- name: CONTINUWUITY_ALLOW_FEDERATION
|
||||
value: "true"
|
||||
- name: CONTINUWUITY_TRUSTED_SERVERS
|
||||
value: '["matrix.org"]'
|
||||
- name: CONTINUWUITY_ADDRESS
|
||||
value: "0.0.0.0"
|
||||
- name: CONTINUWUITY_ROCKSDB_PARALLELISM_THREADS
|
||||
value: "1"
|
||||
- name: CONTINUWUITY_WELL_KNOWN__SERVER
|
||||
value: "matrix.example.com:443"
|
||||
- name: CONTINUWUITY_WELL_KNOWN__CLIENT
|
||||
value: "https://matrix.example.com"
|
||||
- name: CONTINUWUITY_ALLOW_REGISTRATION
|
||||
value: "false"
|
||||
- name: RUST_LOG
|
||||
value: info
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_matrix/federation/v1/version
|
||||
port: http
|
||||
periodSeconds: 4
|
||||
failureThreshold: 5
|
||||
resources:
|
||||
# Continuwuity might use quite some RAM :3
|
||||
requests:
|
||||
cpu: "2"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: "2048Mi"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: continuwuity-data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Apart from manually configuring the containers,
|
||||
[a community-maintained Helm Chart is available here to run
|
||||
conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit)
|
||||
|
||||
This should be compatible with Continuwuity, but you will need to change the image reference.
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
# Troubleshooting Continuwuity
|
||||
|
||||
> **Docker users ⚠️**
|
||||
>
|
||||
> Docker can be difficult to use and debug. It's common for Docker
|
||||
> misconfigurations to cause issues, particularly with networking and permissions.
|
||||
> Please check that your issues are not due to problems with your Docker setup.
|
||||
:::warning{title="Docker users:"}
|
||||
Docker can be difficult to use and debug. It's common for Docker
|
||||
misconfigurations to cause issues, particularly with networking and permissions.
|
||||
Please check that your issues are not due to problems with your Docker setup.
|
||||
:::
|
||||
|
||||
## Continuwuity and Matrix issues
|
||||
|
||||
### Slow joins to rooms
|
||||
|
||||
Some slowness is to be expected if you're the first person on your homserver to join a room (which will
|
||||
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
|
||||
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
|
||||
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
|
||||
`query_trusted_key_servers_first_on_join` is set to true (the default).
|
||||
If you need suggestions for trusted servers, ask in the Continuwuity main room.
|
||||
|
||||
However, _very_ slow joins, especially to rooms with only a few users in them or rooms created by another user
|
||||
on your homeserver, may be caused by [issue !779](https://forgejo.ellis.link/continuwuation/continuwuity/issues/779),
|
||||
which is a longstanding bug with synchronizing room joins to clients. In this situation, you did succeed in joining the room, but
|
||||
the bug caused your homeserver to forget to tell your client. **To fix this, clear your client's cache.** Both Element and Cinny
|
||||
have a button to clear their cache in the "About" section of their settings.
|
||||
|
||||
### Lost access to admin room
|
||||
|
||||
You can reinvite yourself to the admin room through the following methods:
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Setting up TURN/STURN
|
||||
|
||||
In order to make or receive calls, a TURN server is required. Continuwuity suggests
|
||||
using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also
|
||||
available as a Docker image.
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen
|
||||
-s 64 1`.
|
||||
|
||||
These same values need to be set in Continuwuity. See the [example
|
||||
config](./reference/config.mdx) in the TURN section for configuring these and
|
||||
restart Continuwuity after.
|
||||
|
||||
`turn_secret` or a path to `turn_secret_file` must have a value of your
|
||||
coturn `static-auth-secret`, or use `turn_username` and `turn_password`
|
||||
if using legacy username:password TURN authentication (not preferred).
|
||||
|
||||
`turn_uris` must be the list of TURN URIs you would like to send to the client.
|
||||
Typically you will just replace the example domain `example.turn.uri` with the
|
||||
`realm` you set from the example config.
|
||||
|
||||
If you are using TURN over TLS, you can replace `turn:` with `turns:` in the
|
||||
`turn_uris` config option to instruct clients to attempt to connect to
|
||||
TURN over TLS. This is highly recommended.
|
||||
|
||||
If you need unauthenticated access to the TURN URIs, or some clients may be
|
||||
having trouble, you can enable `turn_guest_access` in Continuwuity which disables
|
||||
authentication for the TURN URI endpoint `/_matrix/client/v3/voip/turnServer`
|
||||
|
||||
### Run
|
||||
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
|
||||
```bash
|
||||
docker run -d --network=host -v
|
||||
$(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file
|
||||
called `docker-compose.yml` and run `docker compose up -d` in the same
|
||||
directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative
|
||||
configuration options, please visit [Coturn's Docker
|
||||
documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
|
||||
|
||||
For security recommendations see Synapse's [Coturn
|
||||
documentation](https://element-hq.github.io/synapse/latest/turn-howto.html).
|
||||
|
||||
### Testing
|
||||
|
||||
To make sure turn credentials are being correctly served to clients, you can manually make a HTTP request to the turnServer endpoint.
|
||||
|
||||
`curl "https://<matrix.example.com>/_matrix/client/r0/voip/turnServer" -H 'Authorization: Bearer <your_client_token>' | jq`
|
||||
|
||||
You should get a response like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "1752792167:@jade:example.com",
|
||||
"password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
|
||||
"uris": [
|
||||
"turns:coturn.example.com?transport=udp",
|
||||
"turns:coturn.example.com?transport=tcp",
|
||||
"turn:coturn.example.com?transport=udp",
|
||||
"turn:coturn.example.com?transport=tcp"
|
||||
],
|
||||
"ttl": 86400
|
||||
}
|
||||
```
|
||||
|
||||
You can test these credentials work using [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/)
|
||||
@@ -77,7 +77,12 @@ rec {
|
||||
craneLib.buildDepsOnly (
|
||||
(commonAttrs commonAttrsArgs)
|
||||
// {
|
||||
env = uwuenv.buildDepsOnlyEnv // (makeRocksDBEnv { inherit rocksdb; });
|
||||
env = uwuenv.buildDepsOnlyEnv
|
||||
// (makeRocksDBEnv { inherit rocksdb; })
|
||||
// {
|
||||
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||
};
|
||||
inherit (features) cargoExtraArgs;
|
||||
}
|
||||
|
||||
@@ -102,7 +107,13 @@ rec {
|
||||
'';
|
||||
cargoArtifacts = deps;
|
||||
doCheck = true;
|
||||
env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||
env =
|
||||
uwuenv.buildPackageEnv
|
||||
// rocksdbEnv
|
||||
// {
|
||||
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||
};
|
||||
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||
meta.mainProgram = crateInfo.pname;
|
||||
inherit (features) cargoExtraArgs;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", "replacements:all"],
|
||||
"dependencyDashboard": true,
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
@@ -57,12 +58,25 @@
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "github-actions-non-major"
|
||||
},
|
||||
{
|
||||
"description": "Batch patch-level Node.js dependency updates",
|
||||
"matchManagers": ["npm"],
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"groupName": "node-patch-updates"
|
||||
},
|
||||
{
|
||||
"description": "Pin forgejo artifact actions to prevent breaking changes",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge crate-ci/typos minor updates",
|
||||
"matchPackageNames": ["crate-ci/typos"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward"
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge renovatebot docker image updates",
|
||||
"matchDatasources": ["docker"],
|
||||
|
||||
@@ -56,6 +56,9 @@ export default defineConfig({
|
||||
}, {
|
||||
from: '/community$',
|
||||
to: '/community/guidelines'
|
||||
}, {
|
||||
from: "^/turn",
|
||||
to: "/calls/turn",
|
||||
}
|
||||
]
|
||||
})],
|
||||
|
||||
@@ -89,13 +89,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
locally, if not using get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
%room_id,
|
||||
@@ -235,7 +229,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.resolve_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
@@ -388,13 +382,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
room ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
%room_id,
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(super) async fn list_backups(&self) -> Result {
|
||||
.db
|
||||
.backup_list()?
|
||||
.try_stream()
|
||||
.try_for_each(|result| write!(self, "{result}"))
|
||||
.try_for_each(|result| writeln!(self, "{result}"))
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ gzip_compression = [
|
||||
"conduwuit-service/gzip_compression",
|
||||
"reqwest/gzip",
|
||||
]
|
||||
http3 = [
|
||||
"conduwuit-core/http3",
|
||||
"conduwuit-service/http3",
|
||||
]
|
||||
io_uring = [
|
||||
"conduwuit-service/io_uring",
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Event, Result, debug_info, err, error, info,
|
||||
Err, Event, Result, debug_info, err, error, info,
|
||||
matrix::pdu::PduBuilder,
|
||||
utils::{self, ReadyExt, stream::BroadbandExt},
|
||||
warn,
|
||||
@@ -252,6 +252,13 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow registration with user IDs that aren't local
|
||||
if !services.globals.user_is_local(&user_id) {
|
||||
return Err!(Request(InvalidUsername(
|
||||
"Username {body_username} is not local to this server"
|
||||
)));
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
@@ -380,7 +387,7 @@ pub(crate) async fn register_route(
|
||||
)
|
||||
.await?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
@@ -394,7 +401,7 @@ pub(crate) async fn register_route(
|
||||
&uiaainfo,
|
||||
json,
|
||||
);
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
@@ -654,7 +661,7 @@ pub(crate) async fn change_password_route(
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
// Success!
|
||||
@@ -666,7 +673,7 @@ pub(crate) async fn change_password_route(
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
@@ -784,7 +791,7 @@ pub(crate) async fn deactivate_route(
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
@@ -795,7 +802,7 @@ pub(crate) async fn deactivate_route(
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, debug};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use rand::seq::SliceRandom;
|
||||
use ruma::{
|
||||
OwnedServerName, RoomAliasId, RoomId,
|
||||
api::client::alias::{create_alias, delete_alias, get_alias},
|
||||
};
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::api::client::alias::{create_alias, delete_alias, get_alias};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -96,65 +90,9 @@ pub(crate) async fn get_alias_route(
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
let room_alias = body.body.room_alias;
|
||||
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await
|
||||
else {
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias).await else {
|
||||
return Err!(Request(NotFound("Room with alias not found.")));
|
||||
};
|
||||
|
||||
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
|
||||
debug!(%room_alias, %room_id, "available servers: {servers:?}");
|
||||
|
||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||
}
|
||||
|
||||
async fn room_available_servers(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
room_alias: &RoomAliasId,
|
||||
pre_servers: Vec<OwnedServerName>,
|
||||
) -> Vec<OwnedServerName> {
|
||||
// find active servers in room state cache to suggest
|
||||
let mut servers: Vec<OwnedServerName> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_servers(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
// push any servers we want in the list already (e.g. responded remote alias
|
||||
// servers, room alias server itself)
|
||||
servers.extend(pre_servers);
|
||||
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
|
||||
// shuffle list of servers randomly after sort and dedupe
|
||||
servers.shuffle(&mut rand::thread_rng());
|
||||
|
||||
// insert our server as the very first choice if in list, else check if we can
|
||||
// prefer the room alias server first
|
||||
match servers
|
||||
.iter()
|
||||
.position(|server_name| services.globals.server_is_ours(server_name))
|
||||
{
|
||||
| Some(server_index) => {
|
||||
servers.swap_remove(server_index);
|
||||
servers.insert(0, services.globals.server_name().to_owned());
|
||||
},
|
||||
| _ => {
|
||||
match servers
|
||||
.iter()
|
||||
.position(|server| server == room_alias.server_name())
|
||||
{
|
||||
| Some(alias_server_index) => {
|
||||
servers.swap_remove(alias_server_index);
|
||||
servers.insert(0, room_alias.server_name().into());
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
servers
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{Err, Error, Result, debug, err, utils};
|
||||
use conduwuit::{Err, Result, debug, err, utils};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||
@@ -232,7 +232,7 @@ pub(crate) async fn delete_devices_route(
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
@@ -243,10 +243,10 @@ pub(crate) async fn delete_devices_route(
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug_warn, err,
|
||||
Err, Result, debug, debug_warn, err,
|
||||
result::NotFound,
|
||||
utils,
|
||||
utils::{IterStream, stream::WidebandExt},
|
||||
@@ -215,7 +215,7 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
@@ -226,10 +226,10 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -396,12 +396,12 @@ pub(crate) async fn get_key_changes_route(
|
||||
let from = body
|
||||
.from
|
||||
.parse()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?;
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")))?;
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.parse()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?;
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")))?;
|
||||
|
||||
device_list_updates.extend(
|
||||
services
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, err,
|
||||
Err, Result, err, error,
|
||||
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
|
||||
};
|
||||
use conduwuit_service::{
|
||||
@@ -69,7 +69,7 @@ pub(crate) async fn create_content_route(
|
||||
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
|
||||
.await
|
||||
{
|
||||
err!("Failed to save uploaded media: {e}");
|
||||
error!("Failed to save uploaded media: {e}");
|
||||
return Err!(Request(Unknown("Failed to save uploaded media")));
|
||||
}
|
||||
|
||||
|
||||
@@ -198,11 +198,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
(servers, room_id)
|
||||
},
|
||||
| Err(room_alias) => {
|
||||
let (room_id, mut servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
|
||||
|
||||
banned_room_check(
|
||||
&services,
|
||||
|
||||
@@ -102,11 +102,7 @@ pub(crate) async fn knock_room_route(
|
||||
(servers, room_id)
|
||||
},
|
||||
| Err(room_alias) => {
|
||||
let (room_id, mut servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
|
||||
|
||||
banned_room_check(
|
||||
&services,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, at, debug_warn,
|
||||
Err, Result, at, debug_warn,
|
||||
matrix::{
|
||||
event::{Event, Matches},
|
||||
pdu::PduCount,
|
||||
@@ -322,7 +322,7 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||
|
||||
if server_ignored {
|
||||
// the sender's server is ignored, so ignore this event
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::SenderIgnored { sender: None },
|
||||
"The sender's server is ignored by this server.",
|
||||
));
|
||||
@@ -331,7 +331,7 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
|
||||
// the recipient of this PDU has the sender ignored, and we're not
|
||||
// configured to send ignored messages to clients
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
|
||||
"You have ignored this sender.",
|
||||
));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, err};
|
||||
use conduwuit::{Err, Result, err};
|
||||
use conduwuit_service::Services;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue,
|
||||
@@ -243,27 +243,27 @@ pub(crate) async fn set_pushrule_route(
|
||||
body.before.as_deref(),
|
||||
) {
|
||||
let err = match error {
|
||||
| InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
|
||||
| InsertPushRuleError::ServerDefaultRuleId => err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Rule IDs starting with a dot are reserved for server-default rules.",
|
||||
),
|
||||
| InsertPushRuleError::InvalidRuleId => Error::BadRequest(
|
||||
)),
|
||||
| InsertPushRuleError::InvalidRuleId => err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Rule ID containing invalid characters.",
|
||||
),
|
||||
| InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
|
||||
)),
|
||||
| InsertPushRuleError::RelativeToServerDefaultRule => err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Can't place a push rule relatively to a server-default rule.",
|
||||
),
|
||||
| InsertPushRuleError::UnknownRuleId => Error::BadRequest(
|
||||
)),
|
||||
| InsertPushRuleError::UnknownRuleId => err!(BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"The before or after rule could not be found.",
|
||||
),
|
||||
| InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest(
|
||||
)),
|
||||
| InsertPushRuleError::BeforeHigherThanAfter => err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"The before rule has a higher priority than the after rule.",
|
||||
),
|
||||
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||
)),
|
||||
| _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
|
||||
};
|
||||
|
||||
return Err(err);
|
||||
@@ -433,13 +433,13 @@ pub(crate) async fn delete_pushrule_route(
|
||||
.remove(body.kind.clone(), &body.rule_id)
|
||||
{
|
||||
let err = match error {
|
||||
| RemovePushRuleError::ServerDefault => Error::BadRequest(
|
||||
| RemovePushRuleError::ServerDefault => err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Cannot delete a server-default pushrule.",
|
||||
),
|
||||
)),
|
||||
| RemovePushRuleError::NotFound =>
|
||||
Error::BadRequest(ErrorKind::NotFound, "Push rule not found."),
|
||||
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||
err!(BadRequest(ErrorKind::NotFound, "Push rule not found.")),
|
||||
| _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
|
||||
};
|
||||
|
||||
return Err(err);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
|
||||
use conduwuit_service::Services;
|
||||
use rand::Rng;
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
api::client::{
|
||||
@@ -244,7 +243,7 @@ fn build_report(report: Report) -> RoomMessageEventContent {
|
||||
/// random delay sending a response per spec suggestion regarding
|
||||
/// enumerating for potential events existing in our server.
|
||||
async fn delay_response() {
|
||||
let time_to_wait = rand::thread_rng().gen_range(2..5);
|
||||
let time_to_wait = rand::random_range(2..5);
|
||||
debug_info!(
|
||||
"Got successful /report request, waiting {time_to_wait} seconds before sending \
|
||||
successful response."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Event, Result, RoomVersion, debug, err, info,
|
||||
Err, Event, Result, RoomVersion, debug, err, info,
|
||||
matrix::{StateKey, pdu::PduBuilder},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
@@ -58,7 +58,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if !services.server.supported_room_version(&body.new_version) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::UnsupportedRoomVersion,
|
||||
"This server does not support that room version.",
|
||||
));
|
||||
@@ -170,7 +170,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
"creator".into(),
|
||||
json!(&sender_user).try_into().map_err(|e| {
|
||||
info!("Error forming creation event: {e}");
|
||||
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
|
||||
err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
@@ -186,13 +186,13 @@ pub(crate) async fn upgrade_room_route(
|
||||
"room_version".into(),
|
||||
json!(&body.new_version)
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
|
||||
);
|
||||
create_event_content.insert(
|
||||
"predecessor".into(),
|
||||
json!(predecessor)
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
|
||||
);
|
||||
|
||||
// Validate creation event content
|
||||
@@ -203,7 +203,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||
return Err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||
}
|
||||
|
||||
let create_event_id = services
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, err, info,
|
||||
Err, Result, debug, err, info,
|
||||
utils::{self, ReadyExt, hash},
|
||||
warn,
|
||||
};
|
||||
@@ -191,7 +191,7 @@ pub(crate) async fn handle_login(
|
||||
}
|
||||
|
||||
if services.users.is_locked(&user_id).await? {
|
||||
return Err(Error::BadRequest(ErrorKind::UserLocked, "This account has been locked."));
|
||||
return Err!(BadRequest(ErrorKind::UserLocked, "This account has been locked."));
|
||||
}
|
||||
|
||||
if services.users.is_login_disabled(&user_id).await {
|
||||
@@ -390,7 +390,7 @@ pub(crate) async fn login_token_route(
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
// Success!
|
||||
@@ -402,7 +402,7 @@ pub(crate) async fn login_token_route(
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("No JSON body was sent when required.")));
|
||||
|
||||
@@ -342,10 +342,10 @@ async fn allowed_to_send_state_event(
|
||||
}
|
||||
|
||||
for alias in aliases {
|
||||
let (alias_room_id, _servers) = services
|
||||
let (alias_room_id, _) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&alias, None)
|
||||
.resolve_alias(&alias)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result};
|
||||
use conduwuit::{Result, err};
|
||||
use conduwuit_service::sending::EduBuf;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
@@ -66,7 +66,7 @@ pub(crate) async fn send_event_to_device_route(
|
||||
|
||||
let event = event
|
||||
.deserialize_as()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?;
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Event is invalid")))?;
|
||||
|
||||
match target_device_id_maybe {
|
||||
| DeviceIdOrAllDevices::DeviceId(target_device_id) => {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use conduwuit::{Error, Result};
|
||||
use ruma::api::client::{
|
||||
discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||
discover_support::{self, Contact},
|
||||
},
|
||||
error::ErrorKind,
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::api::client::discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||
discover_support::{self, Contact},
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -19,7 +16,7 @@ pub(crate) async fn well_known_client(
|
||||
) -> Result<discover_homeserver::Response> {
|
||||
let client_url = match services.config.well_known.client.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
| None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
};
|
||||
|
||||
Ok(discover_homeserver::Response {
|
||||
@@ -88,7 +85,7 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
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!(BadRequest(ErrorKind::NotFound, "Not found."));
|
||||
}
|
||||
|
||||
Ok(discover_support::Response { contacts, support_page })
|
||||
@@ -105,7 +102,7 @@ pub(crate) async fn syncv3_client_server_json(
|
||||
| Some(url) => url.to_string(),
|
||||
| None => match services.config.well_known.server.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
| None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -122,23 +122,23 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
// Ruma doesn't have support for multiple paths for a single endpoint yet, and these routes
|
||||
// share one Ruma request / response type pair with {get,send}_state_event_for_key_route
|
||||
.route(
|
||||
"/_matrix/client/r0/rooms/:room_id/state/:event_type",
|
||||
"/_matrix/client/r0/rooms/{room_id}/state/{event_type}",
|
||||
get(client::get_state_events_for_empty_key_route)
|
||||
.put(client::send_state_event_for_empty_key_route),
|
||||
)
|
||||
.route(
|
||||
"/_matrix/client/v3/rooms/:room_id/state/:event_type",
|
||||
"/_matrix/client/v3/rooms/{room_id}/state/{event_type}",
|
||||
get(client::get_state_events_for_empty_key_route)
|
||||
.put(client::send_state_event_for_empty_key_route),
|
||||
)
|
||||
// These two endpoints allow trailing slashes
|
||||
.route(
|
||||
"/_matrix/client/r0/rooms/:room_id/state/:event_type/",
|
||||
"/_matrix/client/r0/rooms/{room_id}/state/{event_type}/",
|
||||
get(client::get_state_events_for_empty_key_route)
|
||||
.put(client::send_state_event_for_empty_key_route),
|
||||
)
|
||||
.route(
|
||||
"/_matrix/client/v3/rooms/:room_id/state/:event_type/",
|
||||
"/_matrix/client/v3/rooms/{room_id}/state/{event_type}/",
|
||||
get(client::get_state_events_for_empty_key_route)
|
||||
.put(client::send_state_event_for_empty_key_route),
|
||||
)
|
||||
@@ -177,7 +177,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::get_mutual_rooms_route)
|
||||
.ruma_route(&client::get_room_summary)
|
||||
.route(
|
||||
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
|
||||
"/_matrix/client/unstable/im.nheko.summary/rooms/{room_id_or_alias}/summary",
|
||||
get(client::get_room_summary_legacy)
|
||||
)
|
||||
.ruma_route(&client::get_suspended_status)
|
||||
@@ -196,7 +196,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&server::get_server_version_route)
|
||||
.route("/_matrix/key/v2/server", get(server::get_server_keys_route))
|
||||
.route(
|
||||
"/_matrix/key/v2/server/:key_id",
|
||||
"/_matrix/key/v2/server/{key_id}",
|
||||
get(server::get_server_keys_deprecated_route),
|
||||
)
|
||||
.ruma_route(&server::get_public_rooms_route)
|
||||
@@ -232,9 +232,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
|
||||
} else {
|
||||
router = router
|
||||
.route("/_matrix/federation/*path", any(federation_disabled))
|
||||
.route("/_matrix/federation/{*path}", any(federation_disabled))
|
||||
.route("/.well-known/matrix/server", any(federation_disabled))
|
||||
.route("/_matrix/key/*path", any(federation_disabled))
|
||||
.route("/_matrix/key/{*path}", any(federation_disabled))
|
||||
.route("/_conduwuit/local_user_count", any(federation_disabled))
|
||||
.route("/_continuwuity/local_user_count", any(federation_disabled));
|
||||
}
|
||||
@@ -253,27 +253,27 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
get(client::get_media_preview_legacy_legacy_route),
|
||||
)
|
||||
.route(
|
||||
"/_matrix/media/v1/download/:server_name/:media_id",
|
||||
"/_matrix/media/v1/download/{server_name}/{media_id}",
|
||||
get(client::get_content_legacy_legacy_route),
|
||||
)
|
||||
.route(
|
||||
"/_matrix/media/v1/download/:server_name/:media_id/:file_name",
|
||||
"/_matrix/media/v1/download/{server_name}/{media_id}/{file_name}",
|
||||
get(client::get_content_as_filename_legacy_legacy_route),
|
||||
)
|
||||
.route(
|
||||
"/_matrix/media/v1/thumbnail/:server_name/:media_id",
|
||||
"/_matrix/media/v1/thumbnail/{server_name}/{media_id}",
|
||||
get(client::get_content_thumbnail_legacy_legacy_route),
|
||||
);
|
||||
} else {
|
||||
router = router
|
||||
.route("/_matrix/media/v1/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v1/{*path}", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/config", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/download/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/thumbnail/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/download/{*path}", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/thumbnail/{*path}", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview))
|
||||
.route("/_matrix/media/r0/config", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/download/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/thumbnail/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/download/{*path}", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/thumbnail/{*path}", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{mem, ops::Deref};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use axum::{body::Body, extract::FromRequest};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
|
||||
@@ -79,7 +78,6 @@ impl<T> Deref for Args<T>
|
||||
fn deref(&self) -> &Self::Target { &self.body }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> FromRequest<State, Body> for Args<T>
|
||||
where
|
||||
T: IncomingRequest + Send + Sync + 'static,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
typed_header::TypedHeaderRejectionReason,
|
||||
};
|
||||
use conduwuit::{Err, Error, Result, debug_error, err, warn};
|
||||
use conduwuit::{Err, Result, debug_error, err, warn};
|
||||
use futures::{
|
||||
TryFutureExt,
|
||||
future::{
|
||||
@@ -54,7 +54,8 @@ pub(super) async fn auth(
|
||||
json_body: Option<&CanonicalJsonValue>,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Auth> {
|
||||
let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?;
|
||||
let bearer: Option<TypedHeader<Authorization<Bearer>>> =
|
||||
request.parts.extract().await.unwrap_or(None);
|
||||
let token = match &bearer {
|
||||
| Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
|
||||
| None => request.query.access_token.as_deref(),
|
||||
@@ -76,7 +77,7 @@ pub(super) async fn auth(
|
||||
// already
|
||||
},
|
||||
| Token::None | Token::Invalid => {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing or invalid access token.",
|
||||
));
|
||||
@@ -95,7 +96,7 @@ pub(super) async fn auth(
|
||||
// already
|
||||
},
|
||||
| Token::None | Token::Invalid => {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing or invalid access token.",
|
||||
));
|
||||
@@ -129,10 +130,10 @@ pub(super) async fn auth(
|
||||
appservice_info: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))
|
||||
Err!(BadRequest(ErrorKind::MissingToken, "Missing access token."))
|
||||
}
|
||||
},
|
||||
| _ => Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")),
|
||||
| _ => Err!(BadRequest(ErrorKind::MissingToken, "Missing access token.")),
|
||||
},
|
||||
| (
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||
@@ -148,7 +149,7 @@ pub(super) async fn auth(
|
||||
&ruma::api::client::session::logout::v3::Request::METADATA
|
||||
| &ruma::api::client::session::logout_all::v3::Request::METADATA
|
||||
) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::UserLocked,
|
||||
"This account has been locked.",
|
||||
));
|
||||
@@ -173,11 +174,11 @@ pub(super) async fn auth(
|
||||
appservice_info: None,
|
||||
}),
|
||||
| (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) =>
|
||||
Err(Error::BadRequest(
|
||||
Err!(BadRequest(
|
||||
ErrorKind::Unauthorized,
|
||||
"Only server signatures should be used on this endpoint.",
|
||||
)),
|
||||
| (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest(
|
||||
| (AuthScheme::AppserviceToken, Token::User(_)) => Err!(BadRequest(
|
||||
ErrorKind::Unauthorized,
|
||||
"Only appservice access tokens should be used on this endpoint.",
|
||||
)),
|
||||
@@ -195,13 +196,13 @@ pub(super) async fn auth(
|
||||
appservice_info: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
Err!(BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
))
|
||||
}
|
||||
},
|
||||
| (_, Token::Invalid) => Err(Error::BadRequest(
|
||||
| (_, Token::Invalid) => Err!(BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
)),
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{borrow::Borrow, iter::once};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt};
|
||||
use conduwuit::{Err, Error, Result, err, info, utils::stream::ReadyExt};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
RoomId,
|
||||
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
|
||||
};
|
||||
use ruma::{RoomId, api::federation::authorization::get_event_authorization};
|
||||
|
||||
use super::AccessCheck;
|
||||
use crate::Ruma;
|
||||
@@ -47,7 +44,7 @@ pub(crate) async fn get_event_authorization_route(
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)
|
||||
.await
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
.map_err(|_| err!(BadRequest(ErrorKind::NotFound, "Event not found.")))?;
|
||||
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use conduwuit::{
|
||||
Err, Error, PduEvent, Result, err, error,
|
||||
Err, PduEvent, Result, err, error,
|
||||
matrix::{Event, event::gen_event_id},
|
||||
utils::{self, hash::sha256},
|
||||
warn,
|
||||
@@ -33,7 +33,7 @@ pub(crate) async fn create_invite_route(
|
||||
.await?;
|
||||
|
||||
if !services.server.supported_room_version(&body.room_version) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() },
|
||||
"Server does not support this room version.",
|
||||
));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::ToOwned;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit::{Err, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
@@ -80,7 +80,7 @@ pub(crate) async fn create_join_event_template_route(
|
||||
|
||||
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
if !body.ver.contains(&room_version_id) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
|
||||
"Room version not supported.",
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use RoomVersionId::*;
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit::{Err, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
|
||||
use ruma::{
|
||||
RoomVersionId,
|
||||
api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
|
||||
@@ -67,14 +67,14 @@ pub(crate) async fn create_knock_event_template_route(
|
||||
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||
|
||||
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
|
||||
"Room version does not support knocking.",
|
||||
));
|
||||
}
|
||||
|
||||
if !body.ver.contains(&room_version_id) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
|
||||
"Your homeserver does not support the features required to knock on this room.",
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{Error, Result};
|
||||
use conduwuit::{Err, Result, err};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::error::ErrorKind,
|
||||
@@ -25,7 +25,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
.config
|
||||
.allow_public_room_directory_over_federation
|
||||
{
|
||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
|
||||
return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
|
||||
}
|
||||
|
||||
let response = crate::client::get_public_rooms_filtered_helper(
|
||||
@@ -38,7 +38,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
|
||||
err!(BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Failed to return this server's public room list."
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(get_public_rooms_filtered::v1::Response {
|
||||
@@ -62,7 +65,7 @@ pub(crate) async fn get_public_rooms_route(
|
||||
.globals
|
||||
.allow_public_room_directory_over_federation()
|
||||
{
|
||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
|
||||
return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
|
||||
}
|
||||
|
||||
let response = crate::client::get_public_rooms_filtered_helper(
|
||||
@@ -75,7 +78,10 @@ pub(crate) async fn get_public_rooms_route(
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
|
||||
err!(BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Failed to return this server's public room list."
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(get_public_rooms::v1::Response {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result, err};
|
||||
use conduwuit::{Err, Result, err};
|
||||
use futures::StreamExt;
|
||||
use get_profile_information::v1::ProfileField;
|
||||
use rand::seq::SliceRandom;
|
||||
@@ -40,7 +40,7 @@ pub(crate) async fn get_room_information_route(
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
|
||||
servers.shuffle(&mut rand::thread_rng());
|
||||
servers.shuffle(&mut rand::rng());
|
||||
|
||||
// insert our server as the very first choice if in list
|
||||
if let Some(server_index) = servers
|
||||
@@ -67,17 +67,16 @@ pub(crate) async fn get_profile_information_route(
|
||||
.config
|
||||
.allow_inbound_profile_lookup_federation_requests
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
"Profile lookup over federation is not allowed on this homeserver.",
|
||||
));
|
||||
}
|
||||
|
||||
if !services.globals.server_is_ours(body.user_id.server_name()) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User does not belong to this server.",
|
||||
));
|
||||
return Err!(
|
||||
BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
|
||||
);
|
||||
}
|
||||
|
||||
let mut displayname = None;
|
||||
|
||||
@@ -114,7 +114,7 @@ pub(crate) async fn send_transaction_message_route(
|
||||
);
|
||||
for (id, result) in &results {
|
||||
if let Err(e) = result {
|
||||
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
|
||||
if matches!(e, Error::BadRequest { kind: ErrorKind::NotFound, .. }) {
|
||||
warn!("Incoming PDU failed {id}: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result};
|
||||
use conduwuit::{Err, Result};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
use ruma::api::{
|
||||
client::error::ErrorKind,
|
||||
@@ -24,7 +24,7 @@ pub(crate) async fn get_devices_route(
|
||||
body: Ruma<get_devices::v1::Request>,
|
||||
) -> Result<get_devices::v1::Response> {
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
@@ -86,10 +86,9 @@ pub(crate) async fn get_keys_route(
|
||||
.iter()
|
||||
.any(|(u, _)| !services.globals.user_is_local(u))
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User does not belong to this server.",
|
||||
));
|
||||
return Err!(
|
||||
BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
|
||||
);
|
||||
}
|
||||
|
||||
let result = get_keys_helper(
|
||||
@@ -121,7 +120,7 @@ pub(crate) async fn claim_keys_route(
|
||||
.iter()
|
||||
.any(|(u, _)| !services.globals.user_is_local(u))
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
return Err!(BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result};
|
||||
use ruma::api::{client::error::ErrorKind, federation::discovery::discover_homeserver};
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::api::federation::discovery::discover_homeserver;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(crate) async fn well_known_server(
|
||||
Ok(discover_homeserver::Response {
|
||||
server: match services.server.config.well_known.server.as_ref() {
|
||||
| Some(server_name) => server_name.to_owned(),
|
||||
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
| None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ conduwuit_mods = [
|
||||
gzip_compression = [
|
||||
"reqwest/gzip",
|
||||
]
|
||||
http3 = [
|
||||
"reqwest/http3",
|
||||
]
|
||||
hardened_malloc = [
|
||||
"dep:hardened_malloc-rs"
|
||||
]
|
||||
@@ -83,6 +86,7 @@ libloading.optional = true
|
||||
log.workspace = true
|
||||
num-traits.workspace = true
|
||||
rand.workspace = true
|
||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
||||
regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
ring.workspace = true
|
||||
@@ -94,7 +98,8 @@ serde-saphyr.workspace = true
|
||||
serde.workspace = true
|
||||
smallvec.workspace = true
|
||||
smallstr.workspace = true
|
||||
thiserror.workspace = true
|
||||
snafu.workspace = true
|
||||
paste.workspace = true
|
||||
tikv-jemallocator.optional = true
|
||||
tikv-jemallocator.workspace = true
|
||||
tikv-jemalloc-ctl.optional = true
|
||||
|
||||
@@ -1244,12 +1244,6 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub rocksdb_repair: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub rocksdb_read_only: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub rocksdb_secondary: bool,
|
||||
|
||||
/// Enables idle CPU priority for compaction thread. This is not enabled by
|
||||
/// default to prevent compaction from falling too far behind on busy
|
||||
/// systems.
|
||||
@@ -1309,26 +1303,33 @@ pub struct Config {
|
||||
|
||||
/// Allow local (your server only) presence updates/requests.
|
||||
///
|
||||
/// Note that presence on continuwuity is very fast unlike Synapse's. If
|
||||
/// using outgoing presence, this MUST be enabled.
|
||||
/// Local presence must be enabled for outgoing presence to function.
|
||||
///
|
||||
/// Note that local presence is not as heavy on the CPU as federated
|
||||
/// presence, but will still become more expensive the more local users you
|
||||
/// have.
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_local_presence: bool,
|
||||
|
||||
/// Allow incoming federated presence updates/requests.
|
||||
/// Allow incoming federated presence updates.
|
||||
///
|
||||
/// This option receives presence updates from other servers, but does not
|
||||
/// send any unless `allow_outgoing_presence` is true. Note that presence on
|
||||
/// continuwuity is very fast unlike Synapse's.
|
||||
/// This option enables processing inbound presence updates from other
|
||||
/// servers. Without it, remote users will appear as if they are always
|
||||
/// offline to your local users. This does not affect typing indicators or
|
||||
/// read receipts.
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_incoming_presence: bool,
|
||||
|
||||
/// Allow outgoing presence updates/requests.
|
||||
///
|
||||
/// This option sends presence updates to other servers, but does not
|
||||
/// receive any unless `allow_incoming_presence` is true. Note that presence
|
||||
/// on continuwuity is very fast unlike Synapse's. If using outgoing
|
||||
/// presence, you MUST enable `allow_local_presence` as well.
|
||||
#[serde(default = "true_fn")]
|
||||
/// This option sends presence updates to other servers, and requires that
|
||||
/// `allow_local_presence` is also enabled.
|
||||
///
|
||||
/// Note that outgoing presence is very heavy on the CPU and network, and
|
||||
/// will typically cause extreme strain and slowdowns for no real benefit.
|
||||
/// There are only a few clients that even implement presence, so you
|
||||
/// probably don't want to enable this.
|
||||
#[serde(default)]
|
||||
pub allow_outgoing_presence: bool,
|
||||
|
||||
/// How many seconds without presence updates before you become idle.
|
||||
@@ -1366,6 +1367,10 @@ pub struct Config {
|
||||
pub allow_incoming_read_receipts: bool,
|
||||
|
||||
/// Allow sending read receipts to remote servers.
|
||||
///
|
||||
/// Note that sending read receipts to remote servers in large rooms with
|
||||
/// lots of other homeservers may cause additional strain on the CPU and
|
||||
/// network.
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_outgoing_read_receipts: bool,
|
||||
|
||||
@@ -1377,6 +1382,10 @@ pub struct Config {
|
||||
pub allow_local_typing: bool,
|
||||
|
||||
/// Allow outgoing typing updates to federation.
|
||||
///
|
||||
/// Note that sending typing indicators to remote servers in large rooms
|
||||
/// with lots of other homeservers may cause additional strain on the CPU
|
||||
/// and network.
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_outgoing_typing: bool,
|
||||
|
||||
@@ -1516,7 +1525,7 @@ pub struct Config {
|
||||
/// sender user's server name, inbound federation X-Matrix origin, and
|
||||
/// outbound federation handler.
|
||||
///
|
||||
/// You can set this to ["*"] to block all servers by default, and then
|
||||
/// You can set this to [".*"] to block all servers by default, and then
|
||||
/// use `allowed_remote_server_names` to allow only specific servers.
|
||||
///
|
||||
/// example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
||||
|
||||
@@ -45,63 +45,162 @@ macro_rules! Err {
|
||||
macro_rules! err {
|
||||
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::Request(
|
||||
$crate::ruma::api::client::error::ErrorKind::forbidden(),
|
||||
$crate::err_log!(buf, $level, $($args)+),
|
||||
$crate::http::StatusCode::BAD_REQUEST
|
||||
)
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
|
||||
message: $crate::err_log!(buf, $level, $($args)+),
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: Some($crate::snafu::Backtrace::capture()),
|
||||
}
|
||||
}};
|
||||
|
||||
(Request(Forbidden($($args:tt)+))) => {
|
||||
$crate::error::Error::Request(
|
||||
$crate::ruma::api::client::error::ErrorKind::forbidden(),
|
||||
$crate::format_maybe!($($args)+),
|
||||
$crate::http::StatusCode::BAD_REQUEST
|
||||
)
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
|
||||
message,
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: Some($crate::snafu::Backtrace::capture()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(Request(NotFound($level:ident!($($args:tt)+)))) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
|
||||
message: $crate::err_log!(buf, $level, $($args)+),
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: None,
|
||||
}
|
||||
}};
|
||||
|
||||
(Request(NotFound($($args:tt)+))) => {
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
|
||||
message,
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(Request($variant:ident($level:ident!($($args:tt)+)))) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::Request(
|
||||
$crate::ruma::api::client::error::ErrorKind::$variant,
|
||||
$crate::err_log!(buf, $level, $($args)+),
|
||||
$crate::http::StatusCode::BAD_REQUEST
|
||||
)
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::$variant,
|
||||
message: $crate::err_log!(buf, $level, $($args)+),
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: Some($crate::snafu::Backtrace::capture()),
|
||||
}
|
||||
}};
|
||||
|
||||
(Request($variant:ident($($args:tt)+))) => {
|
||||
$crate::error::Error::Request(
|
||||
$crate::ruma::api::client::error::ErrorKind::$variant,
|
||||
$crate::format_maybe!($($args)+),
|
||||
$crate::http::StatusCode::BAD_REQUEST
|
||||
)
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::$variant,
|
||||
message,
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: Some($crate::snafu::Backtrace::capture()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(Config($item:literal, $($args:tt)+)) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+))
|
||||
$crate::error::ConfigSnafu {
|
||||
directive: $item,
|
||||
message: $crate::err_log!(buf, error, config = %$item, $($args)+),
|
||||
}.build()
|
||||
}};
|
||||
|
||||
(BadRequest(ErrorKind::NotFound, $($args:tt)+)) => {
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::Error::Request {
|
||||
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
|
||||
message,
|
||||
code: $crate::http::StatusCode::BAD_REQUEST,
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(BadRequest($kind:expr, $($args:tt)+)) => {
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::BadRequestSnafu {
|
||||
kind: $kind,
|
||||
message,
|
||||
}.build()
|
||||
}
|
||||
};
|
||||
|
||||
(FeatureDisabled($($args:tt)+)) => {
|
||||
{
|
||||
let feature: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::FeatureDisabledSnafu { feature }.build()
|
||||
}
|
||||
};
|
||||
|
||||
(Federation($server:expr, $error:expr $(,)?)) => {
|
||||
{
|
||||
$crate::error::FederationSnafu {
|
||||
server: $server,
|
||||
error: $error,
|
||||
}.build()
|
||||
}
|
||||
};
|
||||
|
||||
(InconsistentRoomState($message:expr, $room_id:expr $(,)?)) => {
|
||||
{
|
||||
$crate::error::InconsistentRoomStateSnafu {
|
||||
message: $message,
|
||||
room_id: $room_id,
|
||||
}.build()
|
||||
}
|
||||
};
|
||||
|
||||
(Uiaa($info:expr $(,)?)) => {
|
||||
{
|
||||
$crate::error::UiaaSnafu {
|
||||
info: $info,
|
||||
}.build()
|
||||
}
|
||||
};
|
||||
|
||||
($variant:ident($level:ident!($($args:tt)+))) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::$variant($crate::err_log!(buf, $level, $($args)+))
|
||||
$crate::paste::paste! {
|
||||
$crate::error::[<$variant Snafu>] {
|
||||
message: $crate::err_log!(buf, $level, $($args)+),
|
||||
}.build()
|
||||
}
|
||||
}};
|
||||
|
||||
($variant:ident($($args:ident),+)) => {
|
||||
$crate::error::Error::$variant($($args),+)
|
||||
};
|
||||
|
||||
($variant:ident($($args:tt)+)) => {
|
||||
$crate::error::Error::$variant($crate::format_maybe!($($args)+))
|
||||
$crate::paste::paste! {
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::[<$variant Snafu>] { message }.build()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($level:ident!($($args:tt)+)) => {{
|
||||
let mut buf = String::new();
|
||||
$crate::error::Error::Err($crate::err_log!(buf, $level, $($args)+))
|
||||
let message: std::borrow::Cow<'static, str> = $crate::err_log!(buf, $level, $($args)+);
|
||||
$crate::error::ErrSnafu { message }.build()
|
||||
}};
|
||||
|
||||
($($args:tt)+) => {
|
||||
$crate::error::Error::Err($crate::format_maybe!($($args)+))
|
||||
{
|
||||
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
|
||||
$crate::error::ErrSnafu { message }.build()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,7 +233,7 @@ macro_rules! err_log {
|
||||
};
|
||||
|
||||
($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+));
|
||||
($out).into()
|
||||
std::borrow::Cow::<'static, str>::from($out)
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,151 +6,391 @@
|
||||
|
||||
use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError};
|
||||
|
||||
use snafu::{IntoError, prelude::*};
|
||||
|
||||
pub use self::{err::visit, log::*};
|
||||
|
||||
#[derive(thiserror::Error)]
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[error("PANIC!")]
|
||||
PanicAny(Box<dyn Any + Send>),
|
||||
#[error("PANIC! {0}")]
|
||||
Panic(&'static str, Box<dyn Any + Send + 'static>),
|
||||
#[snafu(display("PANIC!"))]
|
||||
PanicAny {
|
||||
panic: Box<dyn Any + Send>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("PANIC! {message}"))]
|
||||
Panic {
|
||||
message: &'static str,
|
||||
panic: Box<dyn Any + Send + 'static>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
// std
|
||||
#[error(transparent)]
|
||||
Fmt(#[from] std::fmt::Error),
|
||||
#[error(transparent)]
|
||||
FromUtf8(#[from] std::string::FromUtf8Error),
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
ParseFloat(#[from] std::num::ParseFloatError),
|
||||
#[error(transparent)]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
#[error(transparent)]
|
||||
Std(#[from] Box<dyn std::error::Error + Send>),
|
||||
#[error(transparent)]
|
||||
ThreadAccessError(#[from] std::thread::AccessError),
|
||||
#[error(transparent)]
|
||||
TryFromInt(#[from] std::num::TryFromIntError),
|
||||
#[error(transparent)]
|
||||
TryFromSlice(#[from] std::array::TryFromSliceError),
|
||||
#[error(transparent)]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[snafu(display("Format error: {source}"))]
|
||||
Fmt {
|
||||
source: std::fmt::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("UTF-8 conversion error: {source}"))]
|
||||
FromUtf8 {
|
||||
source: std::string::FromUtf8Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("I/O error: {source}"))]
|
||||
Io {
|
||||
source: std::io::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Parse float error: {source}"))]
|
||||
ParseFloat {
|
||||
source: std::num::ParseFloatError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Parse int error: {source}"))]
|
||||
ParseInt {
|
||||
source: std::num::ParseIntError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Error: {source}"))]
|
||||
Std {
|
||||
source: Box<dyn std::error::Error + Send>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Thread access error: {source}"))]
|
||||
ThreadAccessError {
|
||||
source: std::thread::AccessError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Integer conversion error: {source}"))]
|
||||
TryFromInt {
|
||||
source: std::num::TryFromIntError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Slice conversion error: {source}"))]
|
||||
TryFromSlice {
|
||||
source: std::array::TryFromSliceError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("UTF-8 error: {source}"))]
|
||||
Utf8 {
|
||||
source: std::str::Utf8Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
// third-party
|
||||
#[error(transparent)]
|
||||
CapacityError(#[from] arrayvec::CapacityError),
|
||||
#[error(transparent)]
|
||||
CargoToml(#[from] cargo_toml::Error),
|
||||
#[error(transparent)]
|
||||
Clap(#[from] clap::error::Error),
|
||||
#[error(transparent)]
|
||||
Extension(#[from] axum::extract::rejection::ExtensionRejection),
|
||||
#[error(transparent)]
|
||||
Figment(#[from] figment::error::Error),
|
||||
#[error(transparent)]
|
||||
Http(#[from] http::Error),
|
||||
#[error(transparent)]
|
||||
HttpHeader(#[from] http::header::InvalidHeaderValue),
|
||||
#[error("Join error: {0}")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export
|
||||
#[error(transparent)]
|
||||
JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export
|
||||
#[error(transparent)]
|
||||
Path(#[from] axum::extract::rejection::PathRejection),
|
||||
#[error("Mutex poisoned: {0}")]
|
||||
Poison(Cow<'static, str>),
|
||||
#[error("Regex error: {0}")]
|
||||
Regex(#[from] regex::Error),
|
||||
#[error("Request error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
#[error("{0}")]
|
||||
SerdeDe(Cow<'static, str>),
|
||||
#[error("{0}")]
|
||||
SerdeSer(Cow<'static, str>),
|
||||
#[error(transparent)]
|
||||
TomlDe(#[from] toml::de::Error),
|
||||
#[error(transparent)]
|
||||
TomlSer(#[from] toml::ser::Error),
|
||||
#[error("Tracing filter error: {0}")]
|
||||
TracingFilter(#[from] tracing_subscriber::filter::ParseError),
|
||||
#[error("Tracing reload error: {0}")]
|
||||
TracingReload(#[from] tracing_subscriber::reload::Error),
|
||||
#[error(transparent)]
|
||||
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection),
|
||||
#[error(transparent)]
|
||||
YamlDe(#[from] serde_saphyr::Error),
|
||||
#[error(transparent)]
|
||||
YamlSer(#[from] serde_saphyr::ser_error::Error),
|
||||
#[snafu(display("Capacity error: {source}"))]
|
||||
CapacityError {
|
||||
source: arrayvec::CapacityError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Cargo.toml error: {source}"))]
|
||||
CargoToml {
|
||||
source: cargo_toml::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Clap error: {source}"))]
|
||||
Clap {
|
||||
source: clap::error::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Extension rejection: {source}"))]
|
||||
Extension {
|
||||
source: axum::extract::rejection::ExtensionRejection,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Figment error: {source}"))]
|
||||
Figment {
|
||||
source: figment::error::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("HTTP error: {source}"))]
|
||||
Http {
|
||||
source: http::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid HTTP header value: {source}"))]
|
||||
HttpHeader {
|
||||
source: http::header::InvalidHeaderValue,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Join error: {source}"))]
|
||||
JoinError {
|
||||
source: tokio::task::JoinError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("JSON error: {source}"))]
|
||||
Json {
|
||||
source: serde_json::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("JS parse int error: {source}"))]
|
||||
JsParseInt {
|
||||
source: ruma::JsParseIntError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("JS try from int error: {source}"))]
|
||||
JsTryFromInt {
|
||||
source: ruma::JsTryFromIntError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Path rejection: {source}"))]
|
||||
Path {
|
||||
source: axum::extract::rejection::PathRejection,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Mutex poisoned: {message}"))]
|
||||
Poison {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Regex error: {source}"))]
|
||||
Regex {
|
||||
source: regex::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Request error: {source}"))]
|
||||
Reqwest {
|
||||
source: reqwest::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
SerdeDe {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
SerdeSer {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("TOML deserialization error: {source}"))]
|
||||
TomlDe {
|
||||
source: toml::de::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("TOML serialization error: {source}"))]
|
||||
TomlSer {
|
||||
source: toml::ser::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Tracing filter error: {source}"))]
|
||||
TracingFilter {
|
||||
source: tracing_subscriber::filter::ParseError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Tracing reload error: {source}"))]
|
||||
TracingReload {
|
||||
source: tracing_subscriber::reload::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Typed header rejection: {source}"))]
|
||||
TypedHeader {
|
||||
source: axum_extra::typed_header::TypedHeaderRejection,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("YAML deserialization error: {source}"))]
|
||||
YamlDe {
|
||||
source: serde_saphyr::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("YAML serialization error: {source}"))]
|
||||
YamlSer {
|
||||
source: serde_saphyr::ser_error::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
// ruma/conduwuit
|
||||
#[error("Arithmetic operation failed: {0}")]
|
||||
Arithmetic(Cow<'static, str>),
|
||||
#[error("{0}: {1}")]
|
||||
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove
|
||||
#[error("{0}")]
|
||||
BadServerResponse(Cow<'static, str>),
|
||||
#[error(transparent)]
|
||||
CanonicalJson(#[from] ruma::CanonicalJsonError),
|
||||
#[error("There was a problem with the '{0}' directive in your configuration: {1}")]
|
||||
Config(&'static str, Cow<'static, str>),
|
||||
#[error("{0}")]
|
||||
Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists
|
||||
#[error(transparent)]
|
||||
ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError),
|
||||
#[error("{0}")]
|
||||
Database(Cow<'static, str>),
|
||||
#[error("Feature '{0}' is not available on this server.")]
|
||||
FeatureDisabled(Cow<'static, str>),
|
||||
#[error("Remote server {0} responded with: {1}")]
|
||||
Federation(ruma::OwnedServerName, ruma::api::client::error::Error),
|
||||
#[error("{0} in {1}")]
|
||||
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
|
||||
#[error(transparent)]
|
||||
IntoHttp(#[from] ruma::api::error::IntoHttpError),
|
||||
#[error("{0}")]
|
||||
Ldap(Cow<'static, str>),
|
||||
#[error(transparent)]
|
||||
Mxc(#[from] ruma::MxcUriError),
|
||||
#[error(transparent)]
|
||||
Mxid(#[from] ruma::IdParseError),
|
||||
#[error("from {0}: {1}")]
|
||||
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
|
||||
#[error("{0}: {1}")]
|
||||
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
|
||||
#[error(transparent)]
|
||||
Ruma(#[from] ruma::api::client::error::Error),
|
||||
#[error(transparent)]
|
||||
Signatures(#[from] ruma::signatures::Error),
|
||||
#[error(transparent)]
|
||||
StateRes(#[from] crate::state_res::Error),
|
||||
#[error("uiaa")]
|
||||
Uiaa(ruma::api::client::uiaa::UiaaInfo),
|
||||
#[snafu(display("Arithmetic operation failed: {message}"))]
|
||||
Arithmetic {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{kind}: {message}"))]
|
||||
BadRequest {
|
||||
kind: ruma::api::client::error::ErrorKind,
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
BadServerResponse {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Canonical JSON error: {source}"))]
|
||||
CanonicalJson {
|
||||
source: ruma::CanonicalJsonError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"There was a problem with the '{directive}' directive in your configuration: {message}"
|
||||
))]
|
||||
Config {
|
||||
directive: &'static str,
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
Conflict {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Content disposition error: {source}"))]
|
||||
ContentDisposition {
|
||||
source: ruma::http_headers::ContentDispositionParseError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
Database {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Feature '{feature}' is not available on this server."))]
|
||||
FeatureDisabled {
|
||||
feature: Cow<'static, str>,
|
||||
},
|
||||
|
||||
#[snafu(display("Remote server {server} responded with: {error}"))]
|
||||
Federation {
|
||||
server: ruma::OwnedServerName,
|
||||
error: ruma::api::client::error::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message} in {room_id}"))]
|
||||
InconsistentRoomState {
|
||||
message: &'static str,
|
||||
room_id: ruma::OwnedRoomId,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("HTTP conversion error: {source}"))]
|
||||
IntoHttp {
|
||||
source: ruma::api::error::IntoHttpError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{message}"))]
|
||||
Ldap {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("MXC URI error: {source}"))]
|
||||
Mxc {
|
||||
source: ruma::MxcUriError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Matrix ID parse error: {source}"))]
|
||||
Mxid {
|
||||
source: ruma::IdParseError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("from {server}: {error}"))]
|
||||
Redaction {
|
||||
server: ruma::OwnedServerName,
|
||||
error: ruma::canonical_json::RedactionError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("{kind}: {message}"))]
|
||||
Request {
|
||||
kind: ruma::api::client::error::ErrorKind,
|
||||
message: Cow<'static, str>,
|
||||
code: http::StatusCode,
|
||||
backtrace: Option<snafu::Backtrace>,
|
||||
},
|
||||
|
||||
#[snafu(display("Ruma error: {source}"))]
|
||||
Ruma {
|
||||
source: ruma::api::client::error::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Signature error: {source}"))]
|
||||
Signatures {
|
||||
source: ruma::signatures::Error,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("State resolution error: {source}"))]
|
||||
#[snafu(context(false))]
|
||||
StateRes {
|
||||
source: crate::state_res::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("uiaa"))]
|
||||
Uiaa {
|
||||
info: ruma::api::client::uiaa::UiaaInfo,
|
||||
},
|
||||
|
||||
// unique / untyped
|
||||
#[error("{0}")]
|
||||
Err(Cow<'static, str>),
|
||||
#[snafu(display("{message}"))]
|
||||
Err {
|
||||
message: Cow<'static, str>,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) }
|
||||
pub fn from_errno() -> Self { IoSnafu {}.into_error(std::io::Error::last_os_error()) }
|
||||
|
||||
//#[deprecated]
|
||||
#[must_use]
|
||||
pub fn bad_database(message: &'static str) -> Self {
|
||||
crate::err!(Database(error!("{message}")))
|
||||
let message: Cow<'static, str> = message.into();
|
||||
DatabaseSnafu { message }.build()
|
||||
}
|
||||
|
||||
/// Sanitizes public-facing errors that can leak sensitive information.
|
||||
pub fn sanitized_message(&self) -> String {
|
||||
match self {
|
||||
| Self::Database(..) => String::from("Database error occurred."),
|
||||
| Self::Io(..) => String::from("I/O error occurred."),
|
||||
| Self::Database { .. } => String::from("Database error occurred."),
|
||||
| Self::Io { .. } => String::from("I/O error occurred."),
|
||||
| _ => self.message(),
|
||||
}
|
||||
}
|
||||
@@ -158,8 +398,8 @@ pub fn sanitized_message(&self) -> String {
|
||||
/// Generate the error message string.
|
||||
pub fn message(&self) -> String {
|
||||
match self {
|
||||
| Self::Federation(origin, error) => format!("Answer from {origin}: {error}"),
|
||||
| Self::Ruma(error) => response::ruma_error_message(error),
|
||||
| Self::Federation { server, error, .. } => format!("Answer from {server}: {error}"),
|
||||
| Self::Ruma { source, .. } => response::ruma_error_message(source),
|
||||
| _ => format!("{self}"),
|
||||
}
|
||||
}
|
||||
@@ -170,10 +410,10 @@ pub fn kind(&self) -> ruma::api::client::error::ErrorKind {
|
||||
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
|
||||
|
||||
match self {
|
||||
| Self::Federation(_, error) | Self::Ruma(error) =>
|
||||
response::ruma_error_kind(error).clone(),
|
||||
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
|
||||
| Self::FeatureDisabled(..) => FeatureDisabled,
|
||||
| Self::Federation { error, .. } => response::ruma_error_kind(error).clone(),
|
||||
| Self::Ruma { source, .. } => response::ruma_error_kind(source).clone(),
|
||||
| Self::BadRequest { kind, .. } | Self::Request { kind, .. } => kind.clone(),
|
||||
| Self::FeatureDisabled { .. } => FeatureDisabled,
|
||||
| _ => Unknown,
|
||||
}
|
||||
}
|
||||
@@ -184,13 +424,15 @@ pub fn status_code(&self) -> http::StatusCode {
|
||||
use http::StatusCode;
|
||||
|
||||
match self {
|
||||
| Self::Federation(_, error) | Self::Ruma(error) => error.status_code,
|
||||
| Self::Request(kind, _, code) => response::status_code(kind, *code),
|
||||
| Self::BadRequest(kind, ..) => response::bad_request_code(kind),
|
||||
| Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()),
|
||||
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
| Self::Conflict(_) => StatusCode::CONFLICT,
|
||||
| Self::Io(error) => response::io_error_code(error.kind()),
|
||||
| Self::Federation { error, .. } => error.status_code,
|
||||
| Self::Ruma { source, .. } => source.status_code,
|
||||
| Self::Request { kind, code, .. } => response::status_code(kind, *code),
|
||||
| Self::BadRequest { kind, .. } => response::bad_request_code(kind),
|
||||
| Self::FeatureDisabled { .. } => response::bad_request_code(&self.kind()),
|
||||
| Self::Reqwest { source, .. } =>
|
||||
source.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
| Self::Conflict { .. } => StatusCode::CONFLICT,
|
||||
| Self::Io { source, .. } => response::io_error_code(source.kind()),
|
||||
| _ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
@@ -203,16 +445,46 @@ pub fn status_code(&self) -> http::StatusCode {
|
||||
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message())
|
||||
}
|
||||
// Debug is already derived by Snafu
|
||||
|
||||
/// Macro to reduce boilerplate for From implementations using Snafu context
|
||||
macro_rules! impl_from_snafu {
|
||||
($source_ty:ty => $context:ident) => {
|
||||
impl From<$source_ty> for Error {
|
||||
fn from(source: $source_ty) -> Self { $context.into_error(source) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for From impls that format messages into ErrSnafu or other
|
||||
/// message-based contexts
|
||||
macro_rules! impl_from_message {
|
||||
($source_ty:ty => $context:ident, $msg:expr) => {
|
||||
impl From<$source_ty> for Error {
|
||||
fn from(source: $source_ty) -> Self {
|
||||
let message: Cow<'static, str> = format!($msg, source).into();
|
||||
$context { message }.build()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for From impls with constant messages (no formatting)
|
||||
macro_rules! impl_from_const_message {
|
||||
($source_ty:ty => $context:ident, $msg:expr) => {
|
||||
impl From<$source_ty> for Error {
|
||||
fn from(_source: $source_ty) -> Self {
|
||||
let message: Cow<'static, str> = $msg.into();
|
||||
$context { message }.build()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) }
|
||||
fn from(e: PoisonError<T>) -> Self { PoisonSnafu { message: e.to_string() }.build() }
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)]
|
||||
@@ -224,6 +496,43 @@ fn from(_e: Infallible) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations using the macro
|
||||
impl_from_snafu!(std::io::Error => IoSnafu);
|
||||
impl_from_snafu!(std::string::FromUtf8Error => FromUtf8Snafu);
|
||||
impl_from_snafu!(regex::Error => RegexSnafu);
|
||||
impl_from_snafu!(ruma::http_headers::ContentDispositionParseError => ContentDispositionSnafu);
|
||||
impl_from_snafu!(ruma::api::error::IntoHttpError => IntoHttpSnafu);
|
||||
impl_from_snafu!(ruma::JsTryFromIntError => JsTryFromIntSnafu);
|
||||
impl_from_snafu!(ruma::CanonicalJsonError => CanonicalJsonSnafu);
|
||||
impl_from_snafu!(axum::extract::rejection::PathRejection => PathSnafu);
|
||||
impl_from_snafu!(clap::error::Error => ClapSnafu);
|
||||
impl_from_snafu!(ruma::MxcUriError => MxcSnafu);
|
||||
impl_from_snafu!(serde_saphyr::ser_error::Error => YamlSerSnafu);
|
||||
impl_from_snafu!(toml::de::Error => TomlDeSnafu);
|
||||
impl_from_snafu!(http::header::InvalidHeaderValue => HttpHeaderSnafu);
|
||||
impl_from_snafu!(serde_json::Error => JsonSnafu);
|
||||
|
||||
// Custom implementations using message formatting
|
||||
impl_from_const_message!(std::fmt::Error => ErrSnafu, "formatting error");
|
||||
impl_from_message!(std::str::Utf8Error => ErrSnafu, "UTF-8 error: {}");
|
||||
impl_from_message!(std::num::TryFromIntError => ArithmeticSnafu, "integer conversion error: {}");
|
||||
impl_from_message!(tracing_subscriber::reload::Error => ErrSnafu, "tracing reload error: {}");
|
||||
impl_from_message!(reqwest::Error => ErrSnafu, "HTTP client error: {}");
|
||||
impl_from_message!(ruma::signatures::Error => ErrSnafu, "Signature error: {}");
|
||||
impl_from_message!(ruma::IdParseError => ErrSnafu, "ID parse error: {}");
|
||||
impl_from_message!(std::num::ParseIntError => ErrSnafu, "Integer parse error: {}");
|
||||
impl_from_message!(std::array::TryFromSliceError => ErrSnafu, "Slice conversion error: {}");
|
||||
impl_from_message!(tokio::task::JoinError => ErrSnafu, "Task join error: {}");
|
||||
impl_from_message!(serde_saphyr::Error => ErrSnafu, "YAML error: {}");
|
||||
|
||||
// Generic implementation for CapacityError
|
||||
impl<T> From<arrayvec::CapacityError<T>> for Error {
|
||||
fn from(_source: arrayvec::CapacityError<T>) -> Self {
|
||||
let message: Cow<'static, str> = "capacity error: buffer is full".into();
|
||||
ErrSnafu { message }.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
pub fn infallible(_e: &Infallible) {
|
||||
|
||||
@@ -15,13 +15,16 @@ pub fn panic(self) -> ! { panic_any(self.into_panic()) }
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) }
|
||||
pub fn from_panic(e: Box<dyn Any + Send>) -> Self {
|
||||
use super::PanicSnafu;
|
||||
PanicSnafu { message: debug::panic_str(&e), panic: e }.build()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
|
||||
match self {
|
||||
| Self::Panic(_, e) | Self::PanicAny(e) => e,
|
||||
| Self::JoinError(e) => e.into_panic(),
|
||||
| Self::Panic { panic, .. } | Self::PanicAny { panic, .. } => panic,
|
||||
| Self::JoinError { source, .. } => source.into_panic(),
|
||||
| _ => Box::new(self),
|
||||
}
|
||||
}
|
||||
@@ -37,8 +40,8 @@ pub fn panic_str(self) -> Option<&'static str> {
|
||||
#[inline]
|
||||
pub fn is_panic(&self) -> bool {
|
||||
match &self {
|
||||
| Self::Panic(..) | Self::PanicAny(..) => true,
|
||||
| Self::JoinError(e) => e.is_panic(),
|
||||
| Self::Panic { .. } | Self::PanicAny { .. } => true,
|
||||
| Self::JoinError { source, .. } => source.is_panic(),
|
||||
| _ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ fn into_response(self) -> axum::response::Response {
|
||||
impl From<Error> for UiaaResponse {
|
||||
#[inline]
|
||||
fn from(error: Error) -> Self {
|
||||
if let Error::Uiaa(uiaainfo) = error {
|
||||
return Self::AuthResponse(uiaainfo);
|
||||
if let Error::Uiaa { info, .. } = error {
|
||||
return Self::AuthResponse(info);
|
||||
}
|
||||
|
||||
let body = ErrorBody::Standard {
|
||||
|
||||
@@ -5,9 +5,15 @@
|
||||
use crate::Error;
|
||||
|
||||
impl de::Error for Error {
|
||||
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeDe(msg.to_string().into()) }
|
||||
fn custom<T: Display + ToString>(msg: T) -> Self {
|
||||
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
|
||||
super::SerdeDeSnafu { message }.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeSer(msg.to_string().into()) }
|
||||
fn custom<T: Display + ToString>(msg: T) -> Self {
|
||||
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
|
||||
super::SerdeSerSnafu { message }.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
|
||||
use serde_json::{Value as JsonValue, json, value::to_raw_value};
|
||||
|
||||
use crate::{Error, Result, err, implement};
|
||||
use crate::{Result, err, implement};
|
||||
|
||||
#[implement(super::Pdu)]
|
||||
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
|
||||
@@ -10,8 +10,15 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) ->
|
||||
let mut content = serde_json::from_str(self.content.get())
|
||||
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
|
||||
|
||||
redact_content_in_place(&mut content, room_version_id, self.kind.to_string())
|
||||
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
|
||||
redact_content_in_place(&mut content, room_version_id, self.kind.to_string()).map_err(
|
||||
|error| {
|
||||
crate::error::RedactionSnafu {
|
||||
server: self.sender.server_name().to_owned(),
|
||||
error,
|
||||
}
|
||||
.build()
|
||||
},
|
||||
)?;
|
||||
|
||||
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
use crate::{
|
||||
matrix::{Event, Pdu, pdu::EventHash},
|
||||
state_res::{self as state_res, Error, Result, StateMap},
|
||||
state_res::{self as state_res, Error, Result, StateMap, error::NotFoundSnafu},
|
||||
};
|
||||
|
||||
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
|
||||
@@ -170,10 +170,12 @@ fn resolve_deeper_event_set(c: &mut test::Bencher) {
|
||||
#[allow(unused)]
|
||||
impl<E: Event + Clone> TestStore<E> {
|
||||
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
|
||||
self.0
|
||||
.get(event_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::NotFound(format!("{} not found", event_id)))
|
||||
self.0.get(event_id).cloned().ok_or_else(|| {
|
||||
NotFoundSnafu {
|
||||
message: format!("{} not found", event_id),
|
||||
}
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the events that correspond to the `event_ids` sorted in the same
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
use serde_json::Error as JsonError;
|
||||
use thiserror::Error;
|
||||
use snafu::{IntoError, prelude::*};
|
||||
|
||||
/// Represents the various errors that arise when resolving state.
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// A deserialization error.
|
||||
#[error(transparent)]
|
||||
SerdeJson(#[from] JsonError),
|
||||
#[snafu(display("JSON error: {source}"))]
|
||||
SerdeJson {
|
||||
source: JsonError,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
/// The given option or version is unsupported.
|
||||
#[error("Unsupported room version: {0}")]
|
||||
Unsupported(String),
|
||||
#[snafu(display("Unsupported room version: {version}"))]
|
||||
Unsupported {
|
||||
version: String,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
/// The given event was not found.
|
||||
#[error("Not found error: {0}")]
|
||||
NotFound(String),
|
||||
#[snafu(display("Not found error: {message}"))]
|
||||
NotFound {
|
||||
message: String,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
|
||||
/// Invalid fields in the given PDU.
|
||||
#[error("Invalid PDU: {0}")]
|
||||
InvalidPdu(String),
|
||||
#[snafu(display("Invalid PDU: {message}"))]
|
||||
InvalidPdu {
|
||||
message: String,
|
||||
backtrace: snafu::Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(source: serde_json::Error) -> Self { SerdeJsonSnafu.into_error(source) }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
use super::{
|
||||
Error, Event, Result, StateEventType, StateKey, TimelineEventType,
|
||||
error::InvalidPduSnafu,
|
||||
power_levels::{
|
||||
deserialize_power_levels, deserialize_power_levels_content_fields,
|
||||
deserialize_power_levels_content_invite, deserialize_power_levels_content_redact,
|
||||
@@ -383,8 +384,8 @@ pub async fn auth_check<E, F, Fut>(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let target_user =
|
||||
<&UserId>::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{e}")))?;
|
||||
let target_user = <&UserId>::try_from(state_key)
|
||||
.map_err(|e| InvalidPduSnafu { message: format!("{e}") }.build())?;
|
||||
|
||||
let user_for_join_auth = content
|
||||
.join_authorised_via_users_server
|
||||
@@ -461,7 +462,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
?sender_membership_event_content,
|
||||
"Sender membership event content missing membership field"
|
||||
);
|
||||
return Err(Error::InvalidPdu("Missing membership field".to_owned()));
|
||||
return Err(InvalidPduSnafu { message: "Missing membership field" }.build());
|
||||
};
|
||||
let membership_state = membership_state.deserialize()?;
|
||||
|
||||
|
||||
@@ -29,18 +29,18 @@
|
||||
};
|
||||
use serde_json::from_str as from_json_str;
|
||||
|
||||
pub(crate) use self::error::Error;
|
||||
pub(crate) use self::error::{Error, InvalidPduSnafu, NotFoundSnafu};
|
||||
use self::power_levels::PowerLevelsContentFields;
|
||||
pub use self::{
|
||||
event_auth::{auth_check, auth_types_for_event},
|
||||
room_version::RoomVersion,
|
||||
};
|
||||
use super::{Event, StateKey};
|
||||
use crate::{
|
||||
debug, debug_error, err,
|
||||
matrix::{Event, StateKey},
|
||||
debug, debug_error,
|
||||
state_res::room_version::StateResolutionVersion,
|
||||
trace,
|
||||
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt},
|
||||
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt},
|
||||
warn,
|
||||
};
|
||||
|
||||
@@ -118,7 +118,10 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
|
||||
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned())
|
||||
InvalidPduSnafu {
|
||||
message: "Failed to calculate conflicted subgraph",
|
||||
}
|
||||
.build()
|
||||
})?;
|
||||
debug!(count = csg.len(), "conflicted subgraph");
|
||||
trace!(set = ?csg, "conflicted subgraph");
|
||||
@@ -149,10 +152,11 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
|
||||
let control_events: Vec<_> = all_conflicted
|
||||
.iter()
|
||||
.stream()
|
||||
.wide_filter_map(async |id| {
|
||||
is_power_event_id(id, &event_fetch)
|
||||
.broad_filter_map(async |id| {
|
||||
event_fetch(id.clone())
|
||||
.await
|
||||
.then_some(id.clone())
|
||||
.filter(|event| is_power_event(&event))
|
||||
.map(|_| id.clone())
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
@@ -314,7 +318,10 @@ async fn calculate_conflicted_subgraph<F, Fut, E>(
|
||||
trace!(event_id = event_id.as_str(), "fetching event for its auth events");
|
||||
let evt = fetch_event(event_id.clone()).await;
|
||||
if evt.is_none() {
|
||||
err!("could not fetch event {} to calculate conflicted subgraph", event_id);
|
||||
tracing::error!(
|
||||
"could not fetch event {} to calculate conflicted subgraph",
|
||||
event_id
|
||||
);
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
@@ -402,11 +409,11 @@ async fn reverse_topological_power_sort<E, F, Fut>(
|
||||
let fetcher = async |event_id: OwnedEventId| {
|
||||
let pl = *event_to_pl
|
||||
.get(&event_id)
|
||||
.ok_or_else(|| Error::NotFound(String::new()))?;
|
||||
.ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
|
||||
|
||||
let ev = fetch_event(event_id)
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound(String::new()))?;
|
||||
.ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
|
||||
|
||||
Ok((pl, ev.origin_server_ts()))
|
||||
};
|
||||
@@ -612,9 +619,12 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
let events_to_check: Vec<_> = events_to_check
|
||||
.map(Result::Ok)
|
||||
.broad_and_then(async |event_id| {
|
||||
fetch_event(event_id.to_owned())
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}")))
|
||||
fetch_event(event_id.to_owned()).await.ok_or_else(|| {
|
||||
NotFoundSnafu {
|
||||
message: format!("Failed to find {event_id}"),
|
||||
}
|
||||
.build()
|
||||
})
|
||||
})
|
||||
.try_collect()
|
||||
.boxed()
|
||||
@@ -653,7 +663,7 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
trace!(event_id = event.event_id().as_str(), "checking event");
|
||||
let state_key = event
|
||||
.state_key()
|
||||
.ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?;
|
||||
.ok_or_else(|| InvalidPduSnafu { message: "State event had no state key" }.build())?;
|
||||
|
||||
let auth_types = auth_types_for_event(
|
||||
event.event_type(),
|
||||
@@ -669,13 +679,14 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
trace!("room version uses hashed IDs, manually fetching create event");
|
||||
let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$");
|
||||
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
|
||||
Error::InvalidPdu(format!(
|
||||
"Failed to parse create event ID from room ID/hash: {e}"
|
||||
))
|
||||
InvalidPduSnafu {
|
||||
message: format!("Failed to parse create event ID from room ID/hash: {e}"),
|
||||
}
|
||||
.build()
|
||||
})?;
|
||||
let create_event = fetch_event(create_event_id.into()).await.ok_or_else(|| {
|
||||
NotFoundSnafu { message: "Failed to find create event" }.build()
|
||||
})?;
|
||||
let create_event = fetch_event(create_event_id.into())
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound("Failed to find create event".into()))?;
|
||||
auth_state.insert(create_event.event_type().with_state_key(""), create_event);
|
||||
}
|
||||
for aid in event.auth_events() {
|
||||
@@ -686,7 +697,7 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
auth_state.insert(
|
||||
ev.event_type()
|
||||
.with_state_key(ev.state_key().ok_or_else(|| {
|
||||
Error::InvalidPdu("State event had no state key".to_owned())
|
||||
InvalidPduSnafu { message: "State event had no state key" }.build()
|
||||
})?),
|
||||
ev.clone(),
|
||||
);
|
||||
@@ -801,13 +812,13 @@ async fn mainline_sort<E, F, Fut>(
|
||||
|
||||
let event = fetch_event(p.clone())
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {p}")))?;
|
||||
.ok_or_else(|| NotFoundSnafu { message: format!("Failed to find {p}") }.build())?;
|
||||
|
||||
pl = None;
|
||||
for aid in event.auth_events() {
|
||||
let ev = fetch_event(aid.to_owned())
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
||||
let ev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
|
||||
NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
|
||||
})?;
|
||||
|
||||
if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") {
|
||||
pl = Some(aid.to_owned());
|
||||
@@ -869,9 +880,9 @@ async fn get_mainline_depth<E, F, Fut>(
|
||||
|
||||
event = None;
|
||||
for aid in sort_ev.auth_events() {
|
||||
let aev = fetch_event(aid.to_owned())
|
||||
.await
|
||||
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?;
|
||||
let aev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
|
||||
NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
|
||||
})?;
|
||||
|
||||
if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") {
|
||||
event = Some(aev);
|
||||
@@ -915,6 +926,7 @@ async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool
|
||||
where
|
||||
F: Fn(OwnedEventId) -> Fut + Sync,
|
||||
@@ -1046,7 +1058,7 @@ async fn test_event_sort() {
|
||||
// don't remove any events so we know it sorts them all correctly
|
||||
let mut events_to_sort = events.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
events_to_sort.shuffle(&mut rand::thread_rng());
|
||||
events_to_sort.shuffle(&mut rand::rng());
|
||||
|
||||
let power_level = resolved_power
|
||||
.get(&(StateEventType::RoomPowerLevels, "".into()))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruma::RoomVersionId;
|
||||
|
||||
use super::{Error, Result};
|
||||
use super::{Result, error::UnsupportedSnafu};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::exhaustive_enums)]
|
||||
@@ -163,7 +163,11 @@ pub fn new(version: &RoomVersionId) -> Result<Self> {
|
||||
| RoomVersionId::V10 => Self::V10,
|
||||
| RoomVersionId::V11 => Self::V11,
|
||||
| RoomVersionId::V12 => Self::V12,
|
||||
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))),
|
||||
| ver =>
|
||||
return Err(UnsupportedSnafu {
|
||||
version: format!("found version `{ver}`"),
|
||||
}
|
||||
.build()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
|
||||
};
|
||||
|
||||
use super::auth_types_for_event;
|
||||
use super::{auth_types_for_event, error::NotFoundSnafu};
|
||||
use crate::{
|
||||
Result, RoomVersion, info,
|
||||
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
|
||||
@@ -232,7 +232,7 @@ pub(crate) fn get_event(&self, _: &RoomId, event_id: &EventId) -> Result<E> {
|
||||
self.0
|
||||
.get(event_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| super::Error::NotFound(format!("{event_id} not found")))
|
||||
.ok_or_else(|| NotFoundSnafu { message: format!("{event_id} not found") }.build())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
|
||||
pub use ::arrayvec;
|
||||
pub use ::http;
|
||||
pub use ::paste;
|
||||
pub use ::ruma;
|
||||
pub use ::smallstr;
|
||||
pub use ::smallvec;
|
||||
pub use ::snafu;
|
||||
pub use ::toml;
|
||||
pub use ::tracing;
|
||||
pub use config::Config;
|
||||
|
||||
@@ -28,7 +28,7 @@ fn init_argon() -> Argon2<'static> {
|
||||
}
|
||||
|
||||
pub(super) fn password(password: &str) -> Result<String> {
|
||||
let salt = SaltString::generate(rand::thread_rng());
|
||||
let salt = SaltString::generate(rand_core::OsRng);
|
||||
ARGON
|
||||
.get_or_init(init_argon)
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
};
|
||||
|
||||
use arrayvec::ArrayString;
|
||||
use rand::{Rng, seq::SliceRandom, thread_rng};
|
||||
use rand::{RngExt, seq::SliceRandom};
|
||||
|
||||
pub fn shuffle<T>(vec: &mut [T]) {
|
||||
let mut rng = thread_rng();
|
||||
let mut rng = rand::rng();
|
||||
vec.shuffle(&mut rng);
|
||||
}
|
||||
|
||||
pub fn string(length: usize) -> String {
|
||||
thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
rand::rng()
|
||||
.sample_iter(&rand::distr::Alphanumeric)
|
||||
.take(length)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
@@ -22,8 +22,8 @@ pub fn string(length: usize) -> String {
|
||||
#[inline]
|
||||
pub fn string_array<const LENGTH: usize>() -> ArrayString<LENGTH> {
|
||||
let mut ret = ArrayString::<LENGTH>::new();
|
||||
thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
rand::rng()
|
||||
.sample_iter(&rand::distr::Alphanumeric)
|
||||
.take(LENGTH)
|
||||
.map(char::from)
|
||||
.for_each(|c| ret.push(c));
|
||||
@@ -40,7 +40,4 @@ pub fn time_from_now_secs(range: Range<u64>) -> SystemTime {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn secs(range: Range<u64>) -> Duration {
|
||||
let mut rng = thread_rng();
|
||||
Duration::from_secs(rng.gen_range(range))
|
||||
}
|
||||
pub fn secs(range: Range<u64>) -> Duration { Duration::from_secs(rand::random_range(range)) }
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock};
|
||||
|
||||
use snafu::IntoError;
|
||||
|
||||
use crate::{Result, is_equal_to};
|
||||
|
||||
type Id = usize;
|
||||
@@ -142,7 +144,9 @@ pub fn getcpu() -> Result<usize> {
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[inline]
|
||||
pub fn getcpu() -> Result<usize> { Err(crate::Error::Io(std::io::ErrorKind::Unsupported.into())) }
|
||||
pub fn getcpu() -> Result<usize> {
|
||||
Err(crate::error::IoSnafu.into_error(std::io::ErrorKind::Unsupported.into()))
|
||||
}
|
||||
|
||||
fn query_cores_available() -> impl Iterator<Item = Id> {
|
||||
core_affinity::get_core_ids()
|
||||
|
||||
@@ -255,7 +255,10 @@ fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result
|
||||
| "$serde_json::private::RawValue" => visitor.visit_map(self),
|
||||
| "Cbor" => visitor
|
||||
.visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail()))
|
||||
.map_err(|e| Self::Error::SerdeDe(e.to_string().into())),
|
||||
.map_err(|e| {
|
||||
let message: std::borrow::Cow<'static, str> = e.to_string().into();
|
||||
conduwuit_core::error::SerdeDeSnafu { message }.build()
|
||||
}),
|
||||
|
||||
| _ => visitor.visit_newtype_struct(self),
|
||||
}
|
||||
@@ -313,9 +316,10 @@ fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
|
||||
let end = self.pos.saturating_add(BYTES).min(self.buf.len());
|
||||
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
|
||||
let bytes = bytes
|
||||
.into_inner()
|
||||
.map_err(|_| Self::Error::SerdeDe("i64 buffer underflow".into()))?;
|
||||
let bytes = bytes.into_inner().map_err(|_| {
|
||||
let message: std::borrow::Cow<'static, str> = "i64 buffer underflow".into();
|
||||
conduwuit_core::error::SerdeDeSnafu { message }.build()
|
||||
})?;
|
||||
|
||||
self.inc_pos(BYTES);
|
||||
visitor.visit_i64(i64::from_be_bytes(bytes))
|
||||
@@ -345,9 +349,10 @@ fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
|
||||
let end = self.pos.saturating_add(BYTES).min(self.buf.len());
|
||||
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
|
||||
let bytes = bytes
|
||||
.into_inner()
|
||||
.map_err(|_| Self::Error::SerdeDe("u64 buffer underflow".into()))?;
|
||||
let bytes = bytes.into_inner().map_err(|_| {
|
||||
let message: std::borrow::Cow<'static, str> = "u64 buffer underflow".into();
|
||||
conduwuit_core::error::SerdeDeSnafu { message }.build()
|
||||
})?;
|
||||
|
||||
self.inc_pos(BYTES);
|
||||
visitor.visit_u64(u64::from_be_bytes(bytes))
|
||||
|
||||
@@ -33,8 +33,6 @@ pub struct Engine {
|
||||
pub(crate) db: Db,
|
||||
pub(crate) pool: Arc<Pool>,
|
||||
pub(crate) ctx: Arc<Context>,
|
||||
pub(super) read_only: bool,
|
||||
pub(super) secondary: bool,
|
||||
pub(crate) checksums: bool,
|
||||
corks: AtomicU32,
|
||||
}
|
||||
@@ -129,14 +127,6 @@ pub fn current_sequence(&self) -> u64 {
|
||||
|
||||
sequence
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_read_only(&self) -> bool { self.secondary || self.read_only }
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_secondary(&self) -> bool { self.secondary }
|
||||
}
|
||||
|
||||
impl Drop for Engine {
|
||||
|
||||
@@ -12,9 +12,8 @@ pub fn backup(&self) -> Result {
|
||||
let mut engine = self.backup_engine()?;
|
||||
let config = &self.ctx.server.config;
|
||||
if config.database_backups_to_keep > 0 {
|
||||
let flush = !self.is_read_only();
|
||||
engine
|
||||
.create_new_backup_flush(&self.db, flush)
|
||||
.create_new_backup_flush(&self.db, true)
|
||||
.map_err(map_err)?;
|
||||
|
||||
let engine_info = engine.get_backup_info();
|
||||
|
||||
@@ -35,14 +35,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
|
||||
}
|
||||
|
||||
debug!("Opening database...");
|
||||
let db = if config.rocksdb_read_only {
|
||||
Db::open_cf_descriptors_read_only(&db_opts, path, cfds, false)
|
||||
} else if config.rocksdb_secondary {
|
||||
Db::open_cf_descriptors_as_secondary(&db_opts, path, path, cfds)
|
||||
} else {
|
||||
Db::open_cf_descriptors(&db_opts, path, cfds)
|
||||
}
|
||||
.or_else(or_else)?;
|
||||
let db = Db::open_cf_descriptors(&db_opts, path, cfds).or_else(or_else)?;
|
||||
|
||||
info!(
|
||||
columns = num_cfds,
|
||||
@@ -55,8 +48,6 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
|
||||
db,
|
||||
pool: ctx.pool.clone(),
|
||||
ctx: ctx.clone(),
|
||||
read_only: config.rocksdb_read_only,
|
||||
secondary: config.rocksdb_secondary,
|
||||
checksums: config.rocksdb_checksums,
|
||||
corks: AtomicU32::new(0),
|
||||
}))
|
||||
|
||||
@@ -74,14 +74,6 @@ pub fn iter(&self) -> impl Iterator<Item = (&MapsKey, &MapsVal)> + Send + '_ {
|
||||
|
||||
#[inline]
|
||||
pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() }
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_read_only(&self) -> bool { self.db.is_read_only() }
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_secondary(&self) -> bool { self.db.is_secondary() }
|
||||
}
|
||||
|
||||
impl Index<&str> for Database {
|
||||
|
||||
@@ -199,7 +199,10 @@ fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<Se
|
||||
|
||||
value
|
||||
.serialize(&mut Serializer::new(&mut Writer::new(&mut self.out)))
|
||||
.map_err(|e| Self::Error::SerdeSer(e.to_string().into()))
|
||||
.map_err(|e| {
|
||||
let message: std::borrow::Cow<'static, str> = e.to_string().into();
|
||||
conduwuit_core::error::SerdeSerSnafu { message }.build()
|
||||
})
|
||||
},
|
||||
| _ => unhandled!("Unrecognized serialization Newtype {name:?}"),
|
||||
}
|
||||
|
||||
@@ -99,6 +99,11 @@ gzip_compression = [
|
||||
hardened_malloc = [
|
||||
"conduwuit-core/hardened_malloc",
|
||||
]
|
||||
http3 = [
|
||||
"conduwuit-api/http3",
|
||||
"conduwuit-core/http3",
|
||||
"conduwuit-service/http3",
|
||||
]
|
||||
io_uring = [
|
||||
"conduwuit-database/io_uring",
|
||||
]
|
||||
|
||||
@@ -27,10 +27,6 @@ pub struct Args {
|
||||
#[arg(long, short('O'))]
|
||||
pub option: Vec<String>,
|
||||
|
||||
/// Run in a stricter read-only --maintenance mode.
|
||||
#[arg(long)]
|
||||
pub read_only: bool,
|
||||
|
||||
/// Run in maintenance mode while refusing connections.
|
||||
#[arg(long)]
|
||||
pub maintenance: bool,
|
||||
@@ -143,11 +139,7 @@ pub(crate) fn parse() -> Args { Args::parse() }
|
||||
|
||||
/// Synthesize any command line options with configuration file options.
|
||||
pub(crate) fn update(mut config: Figment, args: &Args) -> Result<Figment> {
|
||||
if args.read_only {
|
||||
config = config.join(("rocksdb_read_only", true));
|
||||
}
|
||||
|
||||
if args.maintenance || args.read_only {
|
||||
if args.maintenance {
|
||||
config = config.join(("startup_netburst", false));
|
||||
config = config.join(("listening", false));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use axum::{Router, response::IntoResponse};
|
||||
use conduwuit::Error;
|
||||
@@ -18,5 +18,10 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
|
||||
}
|
||||
|
||||
async fn not_found(_uri: Uri) -> impl IntoResponse {
|
||||
Error::Request(ErrorKind::Unrecognized, "Not Found".into(), StatusCode::NOT_FOUND)
|
||||
Error::Request {
|
||||
kind: ErrorKind::Unrecognized,
|
||||
message: Cow::Borrowed("Not Found"),
|
||||
code: StatusCode::NOT_FOUND,
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ gzip_compression = [
|
||||
"conduwuit-core/gzip_compression",
|
||||
"reqwest/gzip",
|
||||
]
|
||||
http3 = [
|
||||
"conduwuit-core/http3",
|
||||
]
|
||||
io_uring = [
|
||||
"conduwuit-database/io_uring",
|
||||
]
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Result, Server, debug, error, warn};
|
||||
use database::{Deserialized, Map};
|
||||
use rand::Rng;
|
||||
use ruma::events::{Mentions, room::message::RoomMessageEventContent};
|
||||
use serde::Deserialize;
|
||||
use tokio::{
|
||||
@@ -100,8 +99,7 @@ async fn worker(self: Arc<Self>) -> Result<()> {
|
||||
}
|
||||
|
||||
let first_check_jitter = {
|
||||
let mut rng = rand::thread_rng();
|
||||
let jitter_percent = rng.gen_range(-50.0..=10.0);
|
||||
let jitter_percent = rand::random_range(-50.0..=10.0);
|
||||
self.interval.mul_f64(1.0 + jitter_percent / 100.0)
|
||||
};
|
||||
|
||||
|
||||
@@ -147,11 +147,11 @@ pub async fn register_appservice(
|
||||
// same appservice)
|
||||
if let Ok(existing) = self.find_from_token(®istration.as_token).await {
|
||||
if existing.registration.id != registration.id {
|
||||
return Err(err!(Request(InvalidParam(
|
||||
return Err!(Request(InvalidParam(
|
||||
"Cannot register appservice: Token is already used by appservice '{}'. \
|
||||
Please generate a different token.",
|
||||
existing.registration.id
|
||||
))));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,10 +163,10 @@ pub async fn register_appservice(
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err(err!(Request(InvalidParam(
|
||||
return Err!(Request(InvalidParam(
|
||||
"Cannot register appservice: The provided token is already in use by a user \
|
||||
device. Please generate a different token for the appservice."
|
||||
))));
|
||||
)));
|
||||
}
|
||||
|
||||
self.db
|
||||
|
||||
@@ -37,10 +37,6 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
}
|
||||
|
||||
async fn worker(self: Arc<Self>) -> Result {
|
||||
if self.services.globals.is_read_only() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.services.config.ldap.enable {
|
||||
warn!("emergency password feature not available with LDAP enabled.");
|
||||
return Ok(());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use bytes::Bytes;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
|
||||
Err, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
|
||||
error::inspect_debug_log, implement, trace,
|
||||
};
|
||||
use http::{HeaderValue, header::AUTHORIZATION};
|
||||
@@ -179,10 +179,7 @@ async fn into_http_response(
|
||||
|
||||
debug!("Got {status:?} for {method} {url}");
|
||||
if !status.is_success() {
|
||||
return Err(Error::Federation(
|
||||
dest.to_owned(),
|
||||
RumaError::from_http_response(http_response),
|
||||
));
|
||||
return Err!(Federation(dest.to_owned(), RumaError::from_http_response(http_response),));
|
||||
}
|
||||
|
||||
Ok(http_response)
|
||||
|
||||
@@ -156,7 +156,4 @@ pub fn user_is_local(&self, user_id: &UserId) -> bool {
|
||||
pub fn server_is_ours(&self, server_name: &ServerName) -> bool {
|
||||
server_name == self.server_name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_read_only(&self) -> bool { self.db.db.is_read_only() }
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ pub async fn fetch_remote_thumbnail(
|
||||
.fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim)
|
||||
.await;
|
||||
|
||||
if let Err(Error::Request(NotFound, ..)) = &result {
|
||||
if let Err(Error::Request { kind: NotFound, .. }) = &result {
|
||||
return self
|
||||
.fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim)
|
||||
.await;
|
||||
@@ -67,7 +67,7 @@ pub async fn fetch_remote_content(
|
||||
);
|
||||
});
|
||||
|
||||
if let Err(Error::Request(Unrecognized, ..)) = &result {
|
||||
if let Err(Error::Request { kind: Unrecognized, .. }) = &result {
|
||||
return self
|
||||
.fetch_content_unauthenticated(mxc, user, server, timeout_ms)
|
||||
.await;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use conduwuit::{
|
||||
Err, Event, Result, Server, err,
|
||||
Err, Event, Result, err,
|
||||
utils::{ReadyExt, stream::TryIgnore},
|
||||
};
|
||||
use database::{Deserialized, Ignore, Interfix, Map};
|
||||
@@ -30,12 +30,12 @@ struct Data {
|
||||
}
|
||||
|
||||
struct Services {
|
||||
server: Arc<Server>,
|
||||
admin: Dep<admin::Service>,
|
||||
appservice: Dep<appservice::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
sending: Dep<sending::Service>,
|
||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||
state_cache: Dep<rooms::state_cache::Service>,
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
@@ -47,13 +47,13 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
aliasid_alias: args.db["aliasid_alias"].clone(),
|
||||
},
|
||||
services: Services {
|
||||
server: args.server.clone(),
|
||||
admin: args.depend::<admin::Service>("admin"),
|
||||
appservice: args.depend::<appservice::Service>("appservice"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
sending: args.depend::<sending::Service>("sending"),
|
||||
state_accessor: args
|
||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -117,6 +117,9 @@ pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolves the given room ID or alias, returning the resolved room ID.
|
||||
/// Unlike resolve_with_servers (the underlying call), potential resident
|
||||
/// servers are not returned
|
||||
#[inline]
|
||||
pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
|
||||
self.resolve_with_servers(room, None)
|
||||
@@ -124,6 +127,14 @@ pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
|
||||
.map(|(room_id, _)| room_id)
|
||||
}
|
||||
|
||||
/// Resolves the given room ID or alias, returning the resolved room ID, and
|
||||
/// any servers that might be able to assist in fetching room data.
|
||||
///
|
||||
/// If the input is a room ID, this simply returns it and <servers>.
|
||||
/// If the input is an alias, this attempts to resolve it locally, then via
|
||||
/// appservices, and finally remotely if the alias is not local.
|
||||
/// If the alias is successfully resolved, the room ID and an empty list of
|
||||
/// servers is returned.
|
||||
pub async fn resolve_with_servers(
|
||||
&self,
|
||||
room: &RoomOrAliasId,
|
||||
@@ -134,28 +145,26 @@ pub async fn resolve_with_servers(
|
||||
Ok((room_id.to_owned(), servers.unwrap_or_default()))
|
||||
} else {
|
||||
let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId");
|
||||
self.resolve_alias(alias, servers).await
|
||||
self.resolve_alias(alias).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the given room alias, returning the resolved room ID and any
|
||||
/// servers that might be in the room.
|
||||
#[tracing::instrument(skip(self), name = "resolve")]
|
||||
pub async fn resolve_alias(
|
||||
&self,
|
||||
room_alias: &RoomAliasId,
|
||||
servers: Option<Vec<OwnedServerName>>,
|
||||
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
||||
let server_name = room_alias.server_name();
|
||||
let server_is_ours = self.services.globals.server_is_ours(server_name);
|
||||
let servers_contains_ours = || {
|
||||
servers
|
||||
.as_ref()
|
||||
.is_some_and(|servers| servers.contains(&self.services.server.name))
|
||||
};
|
||||
let server_is_ours = self
|
||||
.services
|
||||
.globals
|
||||
.server_is_ours(room_alias.server_name());
|
||||
|
||||
if !server_is_ours && !servers_contains_ours() {
|
||||
return self
|
||||
.remote_resolve(room_alias, servers.unwrap_or_default())
|
||||
.await;
|
||||
if !server_is_ours {
|
||||
// TODO: The spec advises servers may cache remote room aliases temporarily.
|
||||
// We might want to look at doing that.
|
||||
return self.remote_resolve(room_alias).await;
|
||||
}
|
||||
|
||||
let room_id = match self.resolve_local_alias(room_alias).await {
|
||||
@@ -163,10 +172,18 @@ pub async fn resolve_alias(
|
||||
| Err(_) => self.resolve_appservice_alias(room_alias).await?,
|
||||
};
|
||||
|
||||
room_id.map_or_else(
|
||||
|| Err!(Request(NotFound("Room with alias not found."))),
|
||||
|room_id| Ok((room_id, Vec::new())),
|
||||
)
|
||||
if let Some(room_id) = room_id {
|
||||
let servers: Vec<OwnedServerName> = self
|
||||
.services
|
||||
.state_cache
|
||||
.room_servers(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
return Ok((room_id, servers));
|
||||
}
|
||||
|
||||
Err!(Request(NotFound("Alias does not exist.")))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self), level = "debug")]
|
||||
@@ -206,12 +223,12 @@ async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) ->
|
||||
|
||||
// The creator of an alias can remove it
|
||||
if self
|
||||
.who_created_alias(alias).await
|
||||
.is_ok_and(|user| user == user_id)
|
||||
// Server admins can remove any local alias
|
||||
|| self.services.admin.user_is_admin(user_id).await
|
||||
// Always allow the server service account to remove the alias, since there may not be an admin room
|
||||
|| server_user == user_id
|
||||
.who_created_alias(alias).await
|
||||
.is_ok_and(|user| user == user_id)
|
||||
// Server admins can remove any local alias
|
||||
|| self.services.admin.user_is_admin(user_id).await
|
||||
// Always allow the server service account to remove the alias, since there may not be an admin room
|
||||
|| server_user == user_id
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::iter::once;
|
||||
|
||||
use conduwuit::{Result, debug, debug_error, err, implement};
|
||||
use conduwuit::{Result, debug, error, implement};
|
||||
use federation::query::get_room_information::v1::Response;
|
||||
use ruma::{OwnedRoomId, OwnedServerName, RoomAliasId, ServerName, api::federation};
|
||||
|
||||
@@ -8,40 +6,21 @@
|
||||
pub(super) async fn remote_resolve(
|
||||
&self,
|
||||
room_alias: &RoomAliasId,
|
||||
servers: Vec<OwnedServerName>,
|
||||
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
||||
debug!(?room_alias, servers = ?servers, "resolve");
|
||||
let servers = once(room_alias.server_name())
|
||||
.map(ToOwned::to_owned)
|
||||
.chain(servers.into_iter());
|
||||
|
||||
let mut resolved_servers = Vec::new();
|
||||
let mut resolved_room_id: Option<OwnedRoomId> = None;
|
||||
for server in servers {
|
||||
match self.remote_request(room_alias, &server).await {
|
||||
| Err(e) => debug_error!("Failed to query for {room_alias:?} from {server}: {e}"),
|
||||
| Ok(Response { room_id, servers }) => {
|
||||
debug!(
|
||||
"Server {server} answered with {room_id:?} for {room_alias:?} servers: \
|
||||
{servers:?}"
|
||||
);
|
||||
|
||||
resolved_room_id.get_or_insert(room_id);
|
||||
add_server(&mut resolved_servers, server);
|
||||
|
||||
if !servers.is_empty() {
|
||||
add_servers(&mut resolved_servers, servers);
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
debug!("Asking {} to resolve {room_alias:?}", room_alias.server_name());
|
||||
match self
|
||||
.remote_request(room_alias, room_alias.server_name())
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
error!("Unable to resolve remote room alias {}: {e}", room_alias);
|
||||
Err(e)
|
||||
},
|
||||
| Ok(Response { room_id, servers }) => {
|
||||
debug!("Remote resolved {room_alias:?} to {room_id:?} with servers {servers:?}");
|
||||
Ok((room_id, servers))
|
||||
},
|
||||
}
|
||||
|
||||
resolved_room_id
|
||||
.map(|room_id| (room_id, resolved_servers))
|
||||
.ok_or_else(|| {
|
||||
err!(Request(NotFound("No servers could assist in resolving the room alias")))
|
||||
})
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
@@ -59,15 +38,3 @@ async fn remote_request(
|
||||
.send_federation_request(server, request)
|
||||
.await
|
||||
}
|
||||
|
||||
fn add_servers(servers: &mut Vec<OwnedServerName>, new: Vec<OwnedServerName>) {
|
||||
for server in new {
|
||||
add_server(servers, server);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_server(servers: &mut Vec<OwnedServerName>, server: OwnedServerName) {
|
||||
if !servers.contains(&server) {
|
||||
servers.push(server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,14 @@ pub async fn state_resolution<'a, StateSets>(
|
||||
{
|
||||
let event_fetch = |event_id| self.event_fetch(event_id);
|
||||
let event_exists = |event_id| self.event_exists(event_id);
|
||||
state_res::resolve(room_version, state_sets, auth_chain_sets, &event_fetch, &event_exists)
|
||||
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
|
||||
.await
|
||||
Ok(
|
||||
state_res::resolve(
|
||||
room_version,
|
||||
state_sets,
|
||||
auth_chain_sets,
|
||||
&event_fetch,
|
||||
&event_exists,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use conduwuit::{Error, Result};
|
||||
use conduwuit::{Err, Error, Result};
|
||||
use ruma::{UInt, api::client::error::ErrorKind};
|
||||
|
||||
use crate::rooms::short::ShortRoomId;
|
||||
@@ -57,7 +57,7 @@ fn from_str(value: &str) -> Result<Self> {
|
||||
if let Some(token) = pag_tok() {
|
||||
Ok(token)
|
||||
} else {
|
||||
Err(Error::BadRequest(ErrorKind::InvalidParam, "invalid token"))
|
||||
Err!(BadRequest(ErrorKind::InvalidParam, "invalid token"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,12 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let mut federated_room = false;
|
||||
|
||||
while let Some(ref backfill_server) = servers.next().await {
|
||||
if !self.services.globals.server_is_ours(backfill_server) {
|
||||
federated_room = true;
|
||||
}
|
||||
info!("Asking {backfill_server} for backfill in {room_id}");
|
||||
let response = self
|
||||
.services
|
||||
@@ -168,7 +173,9 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
|
||||
}
|
||||
}
|
||||
|
||||
warn!("No servers could backfill, but backfill was needed in room {room_id}");
|
||||
if federated_room {
|
||||
warn!("No servers could backfill, but backfill was needed in room {room_id}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -75,10 +75,7 @@ fn from_evt(
|
||||
let content: RoomCreateEventContent = serde_json::from_str(content.get())?;
|
||||
Ok(content.room_version)
|
||||
} else {
|
||||
Err(Error::InconsistentRoomState(
|
||||
"non-create event for room of unknown version",
|
||||
room_id,
|
||||
))
|
||||
Err!(InconsistentRoomState("non-create event for room of unknown version", room_id))
|
||||
}
|
||||
}
|
||||
let PduBuilder {
|
||||
@@ -275,7 +272,9 @@ fn from_evt(
|
||||
.hash_and_sign_event(&mut pdu_json, &room_version_id)
|
||||
{
|
||||
return match e {
|
||||
| Error::Signatures(ruma::signatures::Error::PduSize) => {
|
||||
| Error::Signatures { source, .. }
|
||||
if matches!(source, ruma::signatures::Error::PduSize) =>
|
||||
{
|
||||
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
|
||||
},
|
||||
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))),
|
||||
|
||||
@@ -385,11 +385,13 @@ fn num_senders(args: &crate::Args<'_>) -> usize {
|
||||
const MIN_SENDERS: usize = 1;
|
||||
// Limit the number of senders to the number of workers threads or number of
|
||||
// cores, conservatively.
|
||||
let max_senders = args
|
||||
.server
|
||||
.metrics
|
||||
.num_workers()
|
||||
.min(available_parallelism());
|
||||
let mut max_senders = args.server.metrics.num_workers();
|
||||
|
||||
// Work around some platforms not returning the number of cores.
|
||||
let num_cores = available_parallelism();
|
||||
if num_cores > 0 {
|
||||
max_senders = max_senders.min(num_cores);
|
||||
}
|
||||
|
||||
// If the user doesn't override the default 0, this is intended to then default
|
||||
// to 1 for now as multiple senders is experimental.
|
||||
|
||||
@@ -139,7 +139,7 @@ pub async fn start(self: &Arc<Self>) -> Result<Arc<Self>> {
|
||||
|
||||
// reset dormant online/away statuses to offline, and set the server user as
|
||||
// online
|
||||
if self.server.config.allow_local_presence && !self.db.is_read_only() {
|
||||
if self.server.config.allow_local_presence {
|
||||
self.presence.unset_all_presence().await;
|
||||
_ = self
|
||||
.presence
|
||||
@@ -156,7 +156,7 @@ pub async fn stop(&self) {
|
||||
info!("Shutting down services...");
|
||||
|
||||
// set the server user as offline
|
||||
if self.server.config.allow_local_presence && !self.db.is_read_only() {
|
||||
if self.server.config.allow_local_presence {
|
||||
_ = self
|
||||
.presence
|
||||
.ping_presence(&self.globals.server_user, &ruma::presence::PresenceState::Offline)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user