Compare commits
155
Commits
ruma-client
...
locks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2423de2d51 | ||
|
|
553fea9a41 | ||
|
|
25c7333112 | ||
|
|
1bba271916 | ||
|
|
ea2fc3adc0 | ||
|
|
b89cffed34 | ||
|
|
3260ae01b8 | ||
|
|
7e0aab7852 | ||
|
|
6e8beb604d | ||
|
|
735d7a0815 | ||
|
|
caa0cbfe1d | ||
|
|
7e9014d5c9 | ||
|
|
bcfea98457 | ||
|
|
3a76fda92b | ||
|
|
318d9c1a35 | ||
|
|
f4aabbdaa7 | ||
|
|
7c82213ee7 | ||
|
|
0719377c6a | ||
|
|
494585267a | ||
|
|
0c23874194 | ||
|
|
dc5f1f41fd | ||
|
|
f81018ab2d | ||
|
|
e5a26de606 | ||
|
|
5ec0be2b41 | ||
|
|
a55dec9035 | ||
|
|
14e6afc45e | ||
|
|
9df86c2c1e | ||
|
|
0753076e94 | ||
|
|
22e3416745 | ||
|
|
787e18b2a4 | ||
|
|
fcc30f059e | ||
|
|
f1a2aa3a94 | ||
|
|
a7cb1c999a | ||
|
|
5711467ad9 | ||
|
|
c30cc50a0b | ||
|
|
5f6b0c673c | ||
|
|
c53cc03ff8 | ||
|
|
af903ceac3 | ||
|
|
f25f61d4a9 | ||
|
|
b2d5516058 | ||
|
|
1bb84a8e2d | ||
|
|
98f1480e2b | ||
|
|
cc9111059d | ||
|
|
05821d6fd5 | ||
|
|
dcac1361ec | ||
|
|
fee7d3d2e3 | ||
|
|
1a70ea5702 | ||
|
|
637d9d3b6f | ||
|
|
808741bcb6 | ||
|
|
b291e76572 | ||
|
|
f3e630c064 | ||
|
|
af2ce5803e | ||
|
|
7fa54e4411 | ||
|
|
f6046871f4 | ||
|
|
2ac7b6d411 | ||
|
|
67f9592b17 | ||
|
|
07271b7725 | ||
|
|
77a23f8969 | ||
|
|
01e01bf25e | ||
|
|
3c9ea55938 | ||
|
|
cf493f2b5b | ||
|
|
e8f6708927 | ||
|
|
8c6bcc47bf | ||
|
|
cd4bc520d8 | ||
|
|
2078af59d8 | ||
|
|
2385bd1cfd | ||
|
|
affa124864 | ||
|
|
ff841b73c5 | ||
|
|
804105479c | ||
|
|
972caacdc2 | ||
|
|
d0ee823254 | ||
|
|
81715bd84d | ||
|
|
b3cec1d20d | ||
|
|
bb7a4220d3 | ||
|
|
b84c66daba | ||
|
|
e1e529d818 | ||
|
|
88cf043f94 | ||
|
|
bff68e595b | ||
|
|
59dd3676d5 | ||
|
|
1fa7ff2eb1 | ||
|
|
deacdf6f58 | ||
|
|
5b5cc0574e | ||
|
|
953f2b005f | ||
|
|
f199b51f97 | ||
|
|
0f16a79888 | ||
|
|
6af942814f | ||
|
|
0805f4ac2a | ||
|
|
7db59c550f | ||
|
|
25b1cd2683 | ||
|
|
daa1fc90a7 | ||
|
|
eeb0b59b76 | ||
|
|
ddcf1a715b | ||
|
|
d1c0eaf628 | ||
|
|
8387ceacab | ||
|
|
1aec80c57c | ||
|
|
aacf6289db | ||
|
|
57ecd81534 | ||
|
|
ceaeab6816 | ||
|
|
e5c7119516 | ||
|
|
1939e62814 | ||
|
|
1ab209736a | ||
|
|
fd69ac621c | ||
|
|
c1b2b468b8 | ||
|
|
7e4dc65747 | ||
|
|
634fe04c33 | ||
|
|
90cd11d850 | ||
|
|
c6625d83e6 | ||
|
|
5a7ccbdfab | ||
|
|
beb428d866 | ||
|
|
52a96b3d84 | ||
|
|
42c9ba2e5c | ||
|
|
47160e9e06 | ||
|
|
488581cc51 | ||
|
|
9b77eb7bb7 | ||
|
|
acfd0aef27 | ||
|
|
50348de1dd | ||
|
|
1b42770ab5 | ||
|
|
3e2f742f30 | ||
|
|
989d843c40 | ||
|
|
09157b2096 | ||
|
|
ae41bc5067 | ||
|
|
8f27e6123b | ||
|
|
24793891e0 | ||
|
|
767fe76d3e | ||
|
|
3ea0d2b567 | ||
|
|
f3e6311680 | ||
|
|
37b4a1cd0d | ||
|
|
68c5c57486 | ||
|
|
f62258ba35 | ||
|
|
ad5e51dc25 | ||
|
|
af6fea3d4e | ||
|
|
a0457000ff | ||
|
|
80f1844476 | ||
|
|
9bf99e99a8 | ||
|
|
442d0732a4 | ||
|
|
9d2cc4d8b1 | ||
|
|
e48cd9b018 | ||
|
|
268ad3441c | ||
|
|
bd16850fd4 | ||
|
|
3408d74f93 | ||
|
|
861cc76363 | ||
|
|
cf94b8e712 | ||
|
|
b42d34858e | ||
|
|
e1c4e5c73e | ||
|
|
8bfaf09f37 | ||
|
|
c28eba1e5b | ||
|
|
08ca573df0 | ||
|
|
61c522dcc0 | ||
|
|
b76af682a6 | ||
|
|
f63a624743 | ||
|
|
4fb2f17b83 | ||
|
|
19aaffeb23 | ||
|
|
f3b1096417 | ||
|
|
5bf43a4d7e | ||
|
|
ab58609d3d |
@@ -59,6 +59,7 @@ $RECYCLE.BIN/
|
||||
# Conduit
|
||||
Rocket.toml
|
||||
conduit.toml
|
||||
conduit.db
|
||||
|
||||
# Etc.
|
||||
**/*.rs.bk
|
||||
|
||||
+179
-4
@@ -1,13 +1,188 @@
|
||||
image: "rust:latest"
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- publish artifacts
|
||||
- release nightly
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
FF_USE_FASTZIP: 1
|
||||
CACHE_COMPRESSION_LEVEL: fastest
|
||||
|
||||
before_script:
|
||||
- apt-get update -yqq
|
||||
- apt-get install -yqq --no-install-recommends build-essential libssl-dev pkg-config
|
||||
|
||||
test:cargo:
|
||||
stage: "test"
|
||||
needs: []
|
||||
image: "rust:latest"
|
||||
variables:
|
||||
CARGO_HOME: "cargohome"
|
||||
cache:
|
||||
paths:
|
||||
- target
|
||||
- cargohome
|
||||
key: test_cache
|
||||
interruptible: true
|
||||
before_script:
|
||||
- mkdir -p $CARGO_HOME && echo "using $CARGO_HOME to cache cargo deps"
|
||||
- apt-get update -yqq
|
||||
- apt-get install -yqq --no-install-recommends build-essential libssl-dev pkg-config
|
||||
- rustup component add clippy rustfmt
|
||||
script:
|
||||
- rustc --version && cargo --version # Print version info for debugging
|
||||
- cargo test --workspace --verbose --locked
|
||||
- cargo fmt --all -- --check
|
||||
- cargo clippy
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Cargo: Compiling for different architectures #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
.build-cargo-shared-settings:
|
||||
stage: "build"
|
||||
needs: []
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
interruptible: true
|
||||
image: "rust:latest"
|
||||
cache:
|
||||
paths:
|
||||
- cargohome
|
||||
- target/
|
||||
key: "build_cache-$TARGET"
|
||||
before_script:
|
||||
- 'echo "Building for target $TARGET"'
|
||||
- 'mkdir -p cargohome && CARGOHOME="cargohome"'
|
||||
- "cat /etc/*-release && rustc --version && cargo --version" # Print version info for debugging
|
||||
- 'apt-get update -yqq'
|
||||
- 'echo "Installing packages: $NEEDED_PACKAGES"'
|
||||
- "apt-get install -yqq --no-install-recommends $NEEDED_PACKAGES"
|
||||
- "rustup target add $TARGET"
|
||||
script:
|
||||
- time cargo build --target $TARGET --release
|
||||
- 'mv "target/$TARGET/release/conduit" "conduit-$TARGET"'
|
||||
artifacts:
|
||||
name: "conduit-$TARGET"
|
||||
expose_as: "Binary"
|
||||
paths:
|
||||
- "conduit-$TARGET"
|
||||
|
||||
build:cargo:x86_64-unknown-linux-gnu:
|
||||
extends: .build-cargo-shared-settings
|
||||
variables:
|
||||
TARGET: "x86_64-unknown-linux-gnu"
|
||||
|
||||
build:cargo:armv7-unknown-linux-gnueabihf:
|
||||
extends: .build-cargo-shared-settings
|
||||
variables:
|
||||
TARGET: "armv7-unknown-linux-gnueabihf"
|
||||
NEEDED_PACKAGES: "build-essential gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-dev-armhf-cross"
|
||||
CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
|
||||
CC_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
|
||||
CXX_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-g++
|
||||
|
||||
build:cargo:aarch64-unknown-linux-gnu:
|
||||
extends: .build-cargo-shared-settings
|
||||
variables:
|
||||
TARGET: "aarch64-unknown-linux-gnu"
|
||||
NEEDED_PACKAGES: "build-essential gcc-8-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross"
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
|
||||
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
|
||||
TARGET_CC: "/usr/bin/aarch64-linux-gnu-gcc-8"
|
||||
TARGET_AR: "/usr/bin/aarch64-linux-gnu-gcc-ar-8"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Cargo: Compiling deb packages for different architectures #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
.build-cargo-deb-shared-settings:
|
||||
stage: "build"
|
||||
needs: []
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
interruptible: true
|
||||
image: "rust:latest"
|
||||
cache:
|
||||
paths:
|
||||
- cargohome
|
||||
- target/
|
||||
key: "build_cache-deb-$TARGET"
|
||||
before_script:
|
||||
- 'echo "Building debian package for target $TARGET"'
|
||||
- 'mkdir -p cargohome && CARGOHOME="cargohome"'
|
||||
- "cat /etc/*-release && rustc --version && cargo --version" # Print version info for debugging
|
||||
- 'apt-get update -yqq'
|
||||
- 'echo "Installing packages: $NEEDED_PACKAGES"'
|
||||
- "apt-get install -yqq --no-install-recommends $NEEDED_PACKAGES"
|
||||
- "rustup target add $TARGET"
|
||||
- "cargo install cargo-deb"
|
||||
script:
|
||||
- time cargo deb --target $TARGET
|
||||
- 'mv target/$TARGET/debian/*.deb "conduit-$TARGET.deb"'
|
||||
artifacts:
|
||||
name: "conduit-$TARGET.deb"
|
||||
expose_as: "Debian Package"
|
||||
paths:
|
||||
- "conduit-$TARGET.deb"
|
||||
|
||||
build:cargo-deb:x86_64-unknown-linux-gnu:
|
||||
extends: .build-cargo-deb-shared-settings
|
||||
variables:
|
||||
TARGET: "x86_64-unknown-linux-gnu"
|
||||
NEEDED_PACKAGES: ""
|
||||
|
||||
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Storing and releasing compiled binaries #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
publish:package:
|
||||
stage: "publish artifacts"
|
||||
needs:
|
||||
- "build:cargo:x86_64-unknown-linux-gnu"
|
||||
- "build:cargo:armv7-unknown-linux-gnueabihf"
|
||||
- "build:cargo:aarch64-unknown-linux-gnu"
|
||||
- "build:cargo-deb:x86_64-unknown-linux-gnu"
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
image: curlimages/curl:latest
|
||||
variables:
|
||||
GIT_STRATEGY: "none" # Don't need a clean copy of the code, we just operate on artifacts
|
||||
script:
|
||||
- 'BASE_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}"'
|
||||
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file conduit-x86_64-unknown-linux-gnu "${BASE_URL}/conduit-x86_64-unknown-linux-gnu"'
|
||||
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file conduit-armv7-unknown-linux-gnueabihf "${BASE_URL}/conduit-armv7-unknown-linux-gnueabihf"'
|
||||
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file conduit-aarch64-unknown-linux-gnu "${BASE_URL}/conduit-aarch64-unknown-linux-gnu"'
|
||||
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file conduit-x86_64-unknown-linux-gnu.deb "${BASE_URL}/conduit-x86_64-unknown-linux-gnu.deb"'
|
||||
|
||||
|
||||
|
||||
publish:nightly:
|
||||
stage: "release nightly"
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
needs:
|
||||
- job: "publish:package"
|
||||
artifacts: false
|
||||
variables:
|
||||
GIT_STRATEGY: "none" # Don't need a clean copy of the code, we just operate on artifacts
|
||||
script: "echo 'Releasing current state as release'"
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" # Run this job on the main branch
|
||||
release:
|
||||
name: 'Nightly'
|
||||
description: '⚠️ The latest development version of the day, fresh from the repository. Use at your own risk!'
|
||||
tag_name: 'nightly'
|
||||
assets:
|
||||
links:
|
||||
- name: 'conduit-x86_64-unknown-linux-gnu'
|
||||
url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}/conduit-x86_64-unknown-linux-gnu"
|
||||
- name: 'conduit-armv7-unknown-linux-gnueabihf'
|
||||
url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}/conduit-armv7-unknown-linux-gnueabihf"
|
||||
- name: 'conduit-aarch64-unknown-linux-gnu'
|
||||
url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}/conduit-aarch64-unknown-linux-gnu"
|
||||
- name: 'conduit-x86_64-unknown-linux-gnu.deb'
|
||||
url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}/conduit-x86_64-unknown-linux-gnu.deb"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Setting up Appservices
|
||||
|
||||
## Getting help
|
||||
|
||||
If you run into any problems while setting up an Appservice, write an email to `timo@koesters.xyz`, ask us in `#conduit:matrix.org` or [open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new).
|
||||
|
||||
## Tested appservices
|
||||
|
||||
Here are some appservices we tested and that work with Conduit:
|
||||
- matrix-appservice-discord
|
||||
- mautrix-hangouts
|
||||
- mautrix-telegram
|
||||
|
||||
## Set up the appservice
|
||||
|
||||
Follow whatever instructions are given by the appservice. This usually includes
|
||||
downloading, changing its config (setting domain, homeserver url, port etc.)
|
||||
and later starting it.
|
||||
|
||||
At some point the appservice guide should ask you to add a registration yaml
|
||||
file to the homeserver. In Synapse you would do this by adding the path to the
|
||||
homeserver.yaml, but in Conduit you can do this from within Matrix:
|
||||
|
||||
First, go into the #admins room of your homeserver. The first person that
|
||||
registered on the homeserver automatically joins it. Then send a message into
|
||||
the room like this:
|
||||
|
||||
@conduit:your.server.name: register_appservice
|
||||
```
|
||||
paste
|
||||
the
|
||||
contents
|
||||
of
|
||||
the
|
||||
yaml
|
||||
registration
|
||||
here
|
||||
```
|
||||
|
||||
You can confirm it worked by sending a message like this:
|
||||
`@conduit:your.server.name: list_appservices`
|
||||
|
||||
The @conduit bot should answer with `Appservices (1): your-bridge`
|
||||
|
||||
Then you are done. Conduit will send messages to the appservices and the
|
||||
appservice can send requests to the homeserver. You don't need to restart
|
||||
Conduit, but if it doesn't work, restarting while the appservice is running
|
||||
could help.
|
||||
Generated
+569
-81
File diff suppressed because it is too large
Load Diff
+20
-13
@@ -15,21 +15,16 @@ edition = "2018"
|
||||
# Used to handle requests
|
||||
# TODO: This can become optional as soon as proper configs are supported
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "801e04bd5369eb39e126c75f6d11e1e9597304d8", features = ["tls"] } # Used to handle requests
|
||||
#rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] }
|
||||
|
||||
# Used for matrix spec type definitions and helpers
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "d27584ae3bdc035529e7389f1c392d4c96f9f8eb", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "220d5b4a76b3b781f7f8297fbe6b14473b04214b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
#ruma = { path = "../ruma/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
|
||||
# Used when doing state resolution
|
||||
state-res = { git = "https://github.com/ruma/state-res", rev = "ce665d213fffeaa47e146d01c6b87f9eb9feaa52", features = ["unstable-pre-spec"] }
|
||||
#state-res = { path = "../state-res", features = ["unstable-pre-spec"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "174555857ef90d49e4b9a672be9e2fe0acdc2687", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
|
||||
# Used for long polling and federation sender, should be the same as rocket::tokio
|
||||
tokio = "1.2.0"
|
||||
# Used for storing data permanently
|
||||
sled = { version = "0.34.6", features = ["compression", "no_metrics"] }
|
||||
sled = { version = "0.34.6", features = ["compression", "no_metrics"], optional = true }
|
||||
rocksdb = { version = "0.16.0", features = ["multi-threaded-cf"], optional = true }
|
||||
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
@@ -51,7 +46,7 @@ rand = "0.8.3"
|
||||
# Used to hash passwords
|
||||
rust-argon2 = "0.8.3"
|
||||
# Used to send requests
|
||||
reqwest = { version = "0.11.3", default-features = false, features = ["rustls-tls-native-roots"] }
|
||||
reqwest = { version = "0.11.3", default-features = false, features = ["rustls-tls-native-roots", "socks"] }
|
||||
# Custom TLS verifier
|
||||
rustls = { version = "0.19", features = ["dangerous_configuration"] }
|
||||
rustls-native-certs = "0.5.0"
|
||||
@@ -77,9 +72,18 @@ tracing-subscriber = "0.2.16"
|
||||
tracing-opentelemetry = "0.11.0"
|
||||
opentelemetry-jaeger = "0.11.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
lru-cache = "0.1.2"
|
||||
rusqlite = { version = "0.25.3", optional = true, features = ["bundled"] }
|
||||
parking_lot = { version = "0.11.1", optional = true }
|
||||
crossbeam = { version = "0.8.1", optional = true }
|
||||
num_cpus = { version = "1.13.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["conduit_bin"]
|
||||
default = ["conduit_bin", "backend_sqlite"]
|
||||
backend_sled = ["sled"]
|
||||
backend_rocksdb = ["rocksdb"]
|
||||
backend_sqlite = ["sqlite"]
|
||||
sqlite = ["rusqlite", "parking_lot", "crossbeam", "num_cpus", "tokio/signal"]
|
||||
conduit_bin = [] # TODO: add rocket to this when it is optional
|
||||
|
||||
[[bin]]
|
||||
@@ -103,13 +107,16 @@ instead of a server that has high scalability."""
|
||||
section = "net"
|
||||
priority = "optional"
|
||||
assets = [
|
||||
["debian/env.local", "etc/matrix-conduit/local", "644"],
|
||||
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["README.md", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
|
||||
]
|
||||
conf-files = [
|
||||
"/etc/matrix-conduit/local"
|
||||
"/etc/matrix-conduit/conduit.toml"
|
||||
]
|
||||
maintainer-scripts = "debian/"
|
||||
systemd-units = { unit-name = "matrix-conduit" }
|
||||
|
||||
# For flamegraphs:
|
||||
#[profile.release]
|
||||
#debug = true
|
||||
|
||||
@@ -6,7 +6,7 @@ If you run into any problems while setting up Conduit, write an email to `timo@k
|
||||
|
||||
## Installing Conduit
|
||||
|
||||
You have to download the binary that fits your machine. Run `uname -m` to see
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see
|
||||
what you need. Now copy the right url:
|
||||
- x84_64: `https://conduit.rs/master/x86_64/conduit-bin`
|
||||
- armv7: `https://conduit.rs/master/armv7/conduit-bin`
|
||||
@@ -18,6 +18,14 @@ $ sudo wget -O /usr/local/bin/matrix-conduit <url>
|
||||
$ sudo chmod +x /usr/local/bin/matrix-conduit
|
||||
```
|
||||
|
||||
Alternatively, you may compile the binary yourself using
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```
|
||||
Note that this currently requires Rust 1.50.
|
||||
|
||||
If you want to cross compile Conduit to another architecture, read the [Cross-Compile Guide](CROSS_COMPILE.md).
|
||||
|
||||
## Adding a Conduit user
|
||||
|
||||
While Conduit can run as any user it is usually better to use dedicated users for different services.
|
||||
@@ -96,8 +104,10 @@ allow_registration = false
|
||||
allow_encryption = true
|
||||
allow_federation = true
|
||||
|
||||
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
|
||||
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#db_cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#workers = 4 # default: cpu core count * 2
|
||||
|
||||
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
|
||||
@@ -207,3 +217,5 @@ Set it to start automatically when your system boots with:
|
||||
```bash
|
||||
$ sudo systemctl enable conduit
|
||||
```
|
||||
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
|
||||
|
||||
+5
-3
@@ -30,7 +30,7 @@ COPY . .
|
||||
RUN if [[ $LOCAL == "true" ]]; then \
|
||||
cargo install --path . ; \
|
||||
else \
|
||||
cargo install --git "https://github.com/timokoesters/conduit.git" --rev ${GIT_REF}; \
|
||||
cargo install --git "https://gitlab.com/famedly/conduit.git" --rev ${GIT_REF}; \
|
||||
fi
|
||||
|
||||
########################## RUNTIME IMAGE ##########################
|
||||
@@ -40,7 +40,9 @@ FROM alpine:3.12
|
||||
|
||||
ARG CREATED
|
||||
ARG VERSION
|
||||
ARG GIT_REF=HEAD
|
||||
ARG GIT_REF=origin/master
|
||||
|
||||
ENV CONDUIT_CONFIG="/srv/conduit/conduit.toml"
|
||||
|
||||
# Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
||||
# including a custom label specifying the build command
|
||||
@@ -52,7 +54,7 @@ LABEL org.opencontainers.image.created=${CREATED} \
|
||||
org.opencontainers.image.description="A Matrix homeserver written in Rust" \
|
||||
org.opencontainers.image.url="https://conduit.rs/" \
|
||||
org.opencontainers.image.revision=${GIT_REF} \
|
||||
org.opencontainers.image.source="https://git.koesters.xyz/timo/conduit.git" \
|
||||
org.opencontainers.image.source="https://gitlab.com/famedly/conduit.git" \
|
||||
org.opencontainers.image.licenses="Apache-2.0" \
|
||||
org.opencontainers.image.documentation="" \
|
||||
org.opencontainers.image.ref.name="" \
|
||||
|
||||
@@ -14,7 +14,7 @@ Yes! Just open a Matrix client (<https://app.element.io> or Element Android for
|
||||
example) and register on the `https://conduit.koesters.xyz` homeserver.
|
||||
|
||||
|
||||
#### What is it build on?
|
||||
#### What is it built on?
|
||||
|
||||
- [Ruma](https://www.ruma.io): Useful structures for endpoint requests and
|
||||
responses that can be (de)serialized
|
||||
@@ -52,15 +52,40 @@ Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit
|
||||
Download or compile a Conduit binary, set up the config and call it from somewhere like a systemd script. [Read
|
||||
more](DEPLOY.md)
|
||||
|
||||
If you want to connect an Appservice to Conduit, take a look at the [Appservice Guide](APPSERVICES.md).
|
||||
|
||||
##### Deploy using a Debian package
|
||||
|
||||
You need to have the `deb` helper command installed that creates Debian packages from Cargo projects (see [cargo-deb](https://github.com/mmstick/cargo-deb/) for more info):
|
||||
|
||||
```shell
|
||||
$ cargo install cargo-deb
|
||||
```
|
||||
|
||||
Then, you can create and install a Debian package at a whim:
|
||||
|
||||
```shell
|
||||
$ cargo deb
|
||||
$ dpkg -i target/debian/matrix-conduit_0.1.0_amd64.deb
|
||||
```
|
||||
|
||||
This will build, package, install, configure and start Conduit. [Read more](debian/README.Debian).
|
||||
|
||||
Note that `cargo deb` supports [cross-compilation](https://github.com/mmstick/cargo-deb/#cross-compilation) too!
|
||||
Official Debian packages will follow once Conduit starts to have stable releases.
|
||||
|
||||
##### Deploy using Docker
|
||||
|
||||
Pull and run the docker image with
|
||||
|
||||
``` bash
|
||||
docker pull matrixconduit/matrix-conduit:latest
|
||||
docker run -d -p 8448:8000 -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
|
||||
docker run -d -p 8448:8000 -v ~/conduit.toml:/srv/conduit/conduit.toml -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
|
||||
```
|
||||
|
||||
> <b>Note:</b> You also need to supply a `conduit.toml` config file, you can find an example [here](./conduit-example.toml).
|
||||
> Or you can pass in `-e CONDUIT_CONFIG=""` and configure Conduit purely with env vars.
|
||||
|
||||
Or build and run it with docker or docker-compose. [Read more](docker/README.md)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# YOU NEED TO EDIT THIS
|
||||
#server_name = "your.server.name"
|
||||
|
||||
# This is the only directly where Conduit will save its data
|
||||
# This is the only directory where Conduit will save its data
|
||||
database_path = "/var/lib/conduit/conduit.db"
|
||||
|
||||
# The port Conduit will be running on. You need to set up a reverse proxy in
|
||||
@@ -33,9 +33,13 @@ max_request_size = 20_000_000 # in bytes
|
||||
# Enable jaeger to support monitoring and troubleshooting through jaeger
|
||||
#allow_jaeger = false
|
||||
|
||||
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
|
||||
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#db_cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#log = "info,state_res=warn,rocket=off,_=off,sled=off"
|
||||
#workers = 4 # default: cpu core count * 2
|
||||
|
||||
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
|
||||
|
||||
proxy = "none" # more examples can be found at src/database/proxy.rs:6
|
||||
|
||||
Vendored
+10
-10
@@ -4,25 +4,25 @@ Conduit for Debian
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
When installed, Debconf handles the configuration of the homeserver (host)name,
|
||||
the address and port it listens on. These configuration variables end up in
|
||||
/etc/matrix-conduit/debian.
|
||||
When installed, Debconf generates the configuration of the homeserver
|
||||
(host)name, the address and port it listens on. This configuration ends up in
|
||||
/etc/matrix-conduit/conduit.toml.
|
||||
|
||||
You can tweak more detailed settings by uncommenting and setting the variables
|
||||
in /etc/matrix-conduit/local. This involves settings such as the maximum file
|
||||
size for download/upload, enabling federation, etc.
|
||||
in /etc/matrix-conduit/conduit.toml. This involves settings such as the maximum
|
||||
file size for download/upload, enabling federation, etc.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The package uses the matrix-conduit.service systemd unit file to start and
|
||||
stop Conduit. It loads the configuration files mentioned above to set up the
|
||||
stop Conduit. It loads the configuration file mentioned above to set up the
|
||||
environment before running the server.
|
||||
|
||||
This package assumes by default that Conduit is placed behind a reverse proxy
|
||||
such as Apache or nginx. This default deployment entails just listening on
|
||||
127.0.0.1 and the free port 14004 and is reachable via a client using the URL
|
||||
http://localhost:14004.
|
||||
This package assumes by default that Conduit will be placed behind a reverse
|
||||
proxy such as Apache or nginx. This default deployment entails just listening
|
||||
on 127.0.0.1 and the free port 6167 and is reachable via a client using the URL
|
||||
http://localhost:6167.
|
||||
|
||||
At a later stage this packaging may support also setting up TLS and running
|
||||
stand-alone. In this case, however, you need to set up some certificates and
|
||||
|
||||
Vendored
-33
@@ -1,33 +0,0 @@
|
||||
# Conduit homeserver local configuration
|
||||
#
|
||||
# Conduit is an application based on the Rocket web framework.
|
||||
# Configuration of Conduit happens via Debconf (see the resulting config in
|
||||
# `/etc/matrix-conduit/debian`) and optionally by uncommenting and tweaking the
|
||||
# variables in this file below.
|
||||
|
||||
# The maximum size of a Matrix HTTP requests in bytes.
|
||||
#
|
||||
# This mostly affects the size of files that can be downloaded/uploaded.
|
||||
# It defaults to 20971520 (20MB).
|
||||
#ROCKET_MAX_REQUEST_SIZE=20971520
|
||||
|
||||
# Whether user registration is allowed.
|
||||
#
|
||||
# User registration is not disabled by default.
|
||||
#ROCKET_REGISTRATION_DISABLED=false
|
||||
|
||||
# Whether encryption is enabled.
|
||||
#
|
||||
# (End-to-end) encryption is not disabled by default.
|
||||
#ROCKET_ENCRYPTION_DISABLED=false
|
||||
|
||||
# Whether federation with other Matrix servers is enabled.
|
||||
#
|
||||
# Federation is not enabled by default; it is still experimental.
|
||||
#ROCKET_FEDERATION_ENABLED=false
|
||||
|
||||
# The log level of the homeserver.
|
||||
#
|
||||
# The log level is "critical" by default.
|
||||
# Allowed values are: "off", "normal", "debug", "critical"
|
||||
#ROCKET_LOG="critical"
|
||||
Vendored
+1
-4
@@ -34,10 +34,7 @@ SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
StateDirectory=matrix-conduit
|
||||
|
||||
Environment="ROCKET_ENV=production"
|
||||
Environment="ROCKET_DATABASE_PATH=/var/lib/matrix-conduit"
|
||||
EnvironmentFile=/etc/matrix-conduit/debian
|
||||
EnvironmentFile=/etc/matrix-conduit/local
|
||||
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
|
||||
|
||||
ExecStart=/usr/sbin/matrix-conduit
|
||||
Restart=on-failure
|
||||
|
||||
Vendored
+50
-38
@@ -4,8 +4,8 @@ set -e
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
|
||||
CONDUIT_CONFIG_FILE="$CONDUIT_CONFIG_PATH/debian"
|
||||
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
|
||||
CONDUIT_CONFIG_FILE="${CONDUIT_CONFIG_PATH}/conduit.toml"
|
||||
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit/conduit_db
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
@@ -13,7 +13,7 @@ case "$1" in
|
||||
if ! getent passwd _matrix-conduit > /dev/null ; then
|
||||
echo 'Adding system user for the Conduit Matrix homeserver' 1>&2
|
||||
adduser --system --group --quiet \
|
||||
--home $CONDUIT_DATABASE_PATH \
|
||||
--home "$CONDUIT_DATABASE_PATH" \
|
||||
--disabled-login \
|
||||
--force-badname \
|
||||
_matrix-conduit
|
||||
@@ -25,48 +25,60 @@ case "$1" in
|
||||
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
||||
fi
|
||||
|
||||
# Write the debconf values in the config.
|
||||
db_get matrix-conduit/hostname
|
||||
ROCKET_SERVER_NAME="$RET"
|
||||
db_get matrix-conduit/address
|
||||
ROCKET_ADDRESS="$RET"
|
||||
db_get matrix-conduit/port
|
||||
ROCKET_PORT="$RET"
|
||||
cat >"$CONDUIT_CONFIG_FILE" << EOF
|
||||
# Conduit homeserver Debian configuration
|
||||
#
|
||||
# Conduit is an application based on the Rocket web framework.
|
||||
# Configuration of Conduit happens via Debconf (of which the resulting config
|
||||
# is in this file) and optionally by uncommenting and tweaking the variables in
|
||||
# /etc/matrix-conduit/local.
|
||||
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
|
||||
# Write the debconf values in the config.
|
||||
db_get matrix-conduit/hostname
|
||||
CONDUIT_SERVER_NAME="$RET"
|
||||
db_get matrix-conduit/address
|
||||
CONDUIT_ADDRESS="$RET"
|
||||
db_get matrix-conduit/port
|
||||
CONDUIT_PORT="$RET"
|
||||
mkdir -p "$CONDUIT_CONFIG_PATH"
|
||||
cat > "$CONDUIT_CONFIG_FILE" << EOF
|
||||
[global]
|
||||
# The server_name is the name of this server. It is used as a suffix for user
|
||||
# and room ids. Examples: matrix.org, conduit.rs
|
||||
# The Conduit server needs to be reachable at https://your.server.name/ on port
|
||||
# 443 (client-server) and 8448 (federation) OR you can create /.well-known
|
||||
# files to redirect requests. See
|
||||
# https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
|
||||
# and https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server
|
||||
# for more information.
|
||||
server_name = "${CONDUIT_SERVER_NAME}"
|
||||
|
||||
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN!
|
||||
#
|
||||
# Please make changes by running:
|
||||
#
|
||||
# \$ dpkg-reconfigure matrix-conduit
|
||||
#
|
||||
# or by providing overriding changes in /etc/matrix-conduit/local.
|
||||
# This is the only directory where Conduit will save its data.
|
||||
database_path = "${CONDUIT_DATABASE_PATH}"
|
||||
|
||||
# The server (host)name of the Matrix homeserver.
|
||||
#
|
||||
# This is the hostname the homeserver will be reachable at via a client.
|
||||
ROCKET_SERVER_NAME="$ROCKET_SERVER_NAME"
|
||||
|
||||
# The address the Matrix homeserver listens on.
|
||||
#
|
||||
# The address Conduit will be listening on.
|
||||
# By default the server listens on address 0.0.0.0. Change this to 127.0.0.1 to
|
||||
# only listen on the localhost when using a reverse proxy.
|
||||
ROCKET_ADDRESS="$ROCKET_ADDRESS"
|
||||
address = "${CONDUIT_ADDRESS}"
|
||||
|
||||
# The port of the Matrix homeserver.
|
||||
#
|
||||
# This port is could be any available port if accessed by a reverse proxy.
|
||||
# By default the server listens on port 8000.
|
||||
ROCKET_PORT="$ROCKET_PORT"
|
||||
# The port Conduit will be running on. You need to set up a reverse proxy in
|
||||
# your web server (e.g. apache or nginx), so all requests to /_matrix on port
|
||||
# 443 and 8448 will be forwarded to the Conduit instance running on this port.
|
||||
port = ${CONDUIT_PORT}
|
||||
|
||||
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN!
|
||||
# Max size for uploads
|
||||
max_request_size = 20_000_000 # in bytes
|
||||
|
||||
# Disable registration. No new users will be able to register on this server.
|
||||
#allow_registration = false
|
||||
|
||||
# Disable encryption, so no new encrypted rooms can be created.
|
||||
# Note: Existing rooms will continue to work.
|
||||
#allow_encryption = false
|
||||
#allow_federation = false
|
||||
|
||||
# Enable jaeger to support monitoring and troubleshooting through jaeger.
|
||||
#allow_jaeger = false
|
||||
|
||||
#db_cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#log = "info,state_res=warn,rocket=off,_=off,sled=off"
|
||||
#workers = 4 # default: cpu core count * 2
|
||||
EOF
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
Vendored
+5
@@ -1,11 +1,16 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
|
||||
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
|
||||
|
||||
case $1 in
|
||||
purge)
|
||||
# Remove debconf changes from the db
|
||||
db_purge
|
||||
|
||||
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
|
||||
# "configuration files must be preserved when the package is removed, and
|
||||
# only deleted when the package is purged."
|
||||
|
||||
Vendored
+1
-1
@@ -16,6 +16,6 @@ Description: The listen address of the Matrix homeserver
|
||||
|
||||
Template: matrix-conduit/port
|
||||
Type: string
|
||||
Default: 14004
|
||||
Default: 6167
|
||||
Description: The port of the Matrix homeserver
|
||||
This port is most often just accessed by a reverse proxy.
|
||||
|
||||
+27
-22
@@ -5,43 +5,48 @@ services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: matrixconduit/matrix-conduit:latest
|
||||
#image: matrixconduit/matrix-conduit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED:
|
||||
# VERSION:
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: HEAD
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
CREATED: '2021-03-16T08:18:27Z'
|
||||
VERSION: '0.1.0'
|
||||
LOCAL: 'false'
|
||||
GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:8000
|
||||
volumes:
|
||||
- db:/srv/conduit/.local/share/conduit
|
||||
### Uncomment if you want to use Rocket.toml to configure Conduit
|
||||
### Note: Set env vars will override Rocket.toml values
|
||||
# - ./Rocket.toml:/srv/conduit/Rocket.toml
|
||||
### Uncomment if you want to use conduit.toml to configure Conduit
|
||||
### Note: Set env vars will override conduit.toml values
|
||||
# - ./conduit.toml:/srv/conduit/conduit.toml
|
||||
environment:
|
||||
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name
|
||||
CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
### Uncomment and change values as desired
|
||||
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical
|
||||
# ROCKET_PORT: 8000
|
||||
# ROCKET_REGISTRATION_DISABLED: 'true'
|
||||
# ROCKET_ENCRYPTION_DISABLED: 'true'
|
||||
# ROCKET_FEDERATION_ENABLED: 'true'
|
||||
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# ROCKET_WORKERS: 10
|
||||
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
# CONDUIT_ADDRESS: 127.0.0.1
|
||||
# CONDUIT_PORT: 8000
|
||||
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if you want to configure purely by env vars, set this to an empty string ''
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
|
||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUIT_ALLOW_REGISTRATION : 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'false'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'false'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/riot-web:latest
|
||||
# image: vectorim/element-web:latest
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - 8009:80
|
||||
@@ -51,4 +56,4 @@ services:
|
||||
# - homeserver
|
||||
|
||||
volumes:
|
||||
db:
|
||||
db:
|
||||
|
||||
+6
-4
@@ -17,13 +17,13 @@ The Dockerfile includes a few build arguments that should be supplied when build
|
||||
ARG LOCAL=false
|
||||
ARG CREATED
|
||||
ARG VERSION
|
||||
ARG GIT_REF=HEAD
|
||||
ARG GIT_REF=origin/master
|
||||
```
|
||||
|
||||
- **CREATED:** Date and time as string (date-time as defined by RFC 3339). Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.created`. Supply by it like this `$(date -u +'%Y-%m-%dT%H:%M:%SZ')`
|
||||
- **VERSION:** The SemVer version of Conduit, which is in the image. Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.version`. If you have a `Cargo.toml` in your build context, you can get it with `$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)`
|
||||
- **LOCAL:** *(Optional)* A boolean value, specifies if the local build context should be used, or if the official repository will be cloned. If not supplied with the build command, it will default to `false`.
|
||||
- **GIT_REF:** *(Optional)* A git ref, like `HEAD` or a commit ID. The supplied ref will be used to create the Open Container Initiative compliant label `org.opencontainers.image.revision` and will be the ref that is cloned from the repository when not building from the local context. If not supplied with the build command, it will default to `HEAD`.
|
||||
- **GIT_REF:** *(Optional)* A git ref, like `HEAD` or a commit ID. The supplied ref will be used to create the Open Container Initiative compliant label `org.opencontainers.image.revision` and will be the ref that is cloned from the repository when not building from the local context. If not supplied with the build command, it will default to `origin/master`.
|
||||
|
||||
To build the image you can use the following command
|
||||
|
||||
@@ -40,10 +40,12 @@ which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`
|
||||
After building the image you can simply run it with
|
||||
|
||||
``` bash
|
||||
docker run -d -p 8448:8000 -v db:/srv/conduit/.local/share/conduit -e ROCKET_SERVER_NAME="localhost:8000" matrixconduit/matrix-conduit:latest
|
||||
docker run -d -p 8448:8000 -v ~/conduit.toml:/srv/conduit/conduit.toml -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
|
||||
```
|
||||
|
||||
For detached mode, you also need to use the `-d` flag. You can pass in more env vars as are shown here, for an overview of possible values, you can take a look at the `docker-compose.yml` file.
|
||||
For detached mode, you also need to use the `-d` flag. You also need to supply a `conduit.toml` config file, you can find an example [here](../conduit-example.toml).
|
||||
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
|
||||
too pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
|
||||
If you just want to test Conduit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
|
||||
|
||||
|
||||
|
||||
@@ -15,32 +15,38 @@ services:
|
||||
# CREATED:
|
||||
# VERSION:
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: HEAD
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/srv/conduit/.local/share/conduit
|
||||
### Uncomment if you want to use Rocket.toml to configure Conduit
|
||||
### Note: Set env vars will override Rocket.toml values
|
||||
# - ./Rocket.toml:/srv/conduit/Rocket.toml
|
||||
### Uncomment if you want to use conduit.toml to configure Conduit
|
||||
### Note: Set env vars will override conduit.toml values
|
||||
# - ./conduit.toml:/srv/conduit/conduit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name
|
||||
CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
### Uncomment and change values as desired
|
||||
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical
|
||||
# ROCKET_PORT: 8000
|
||||
# ROCKET_REGISTRATION_DISABLED: 'true'
|
||||
# ROCKET_ENCRYPTION_DISABLED: 'true'
|
||||
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# ROCKET_WORKERS: 10
|
||||
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
# CONDUIT_ADDRESS: 127.0.0.1
|
||||
# CONDUIT_PORT: 8000
|
||||
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if want to configure purely by env vars, set this to an empty string ''
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
|
||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUIT_ALLOW_REGISTRATION : 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'false'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'false'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/riot-web:latest
|
||||
# image: vectorim/element-web:latest
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - ./element_config.json:/app/config.json
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.50.0
|
||||
1.51.0
|
||||
|
||||
+15
-21
@@ -1,11 +1,11 @@
|
||||
use crate::{utils, Error, Result};
|
||||
use bytes::BytesMut;
|
||||
use http::header::{HeaderValue, CONTENT_TYPE};
|
||||
use log::warn;
|
||||
use ruma::api::{IncomingResponse, OutgoingRequest, SendAccessToken};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
mem,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -40,33 +40,27 @@ where
|
||||
);
|
||||
*http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid");
|
||||
|
||||
http_request.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_str("application/json").unwrap(),
|
||||
);
|
||||
|
||||
let mut reqwest_request = reqwest::Request::try_from(http_request)
|
||||
.expect("all http requests are valid reqwest requests");
|
||||
|
||||
*reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
|
||||
|
||||
let url = reqwest_request.url().clone();
|
||||
let mut reqwest_response = globals.reqwest_client().execute(reqwest_request).await?;
|
||||
let mut response = globals.reqwest_client().execute(reqwest_request).await?;
|
||||
|
||||
// Because reqwest::Response -> http::Response is complicated:
|
||||
let status = reqwest_response.status();
|
||||
let mut http_response = http::Response::builder().status(status);
|
||||
let headers = http_response.headers_mut().unwrap();
|
||||
// reqwest::Response -> http::Response conversion
|
||||
let status = response.status();
|
||||
let mut http_response_builder = http::Response::builder()
|
||||
.status(status)
|
||||
.version(response.version());
|
||||
mem::swap(
|
||||
response.headers_mut(),
|
||||
http_response_builder
|
||||
.headers_mut()
|
||||
.expect("http::response::Builder is usable"),
|
||||
);
|
||||
|
||||
for (k, v) in reqwest_response.headers_mut().drain() {
|
||||
if let Some(key) = k {
|
||||
headers.insert(key, v);
|
||||
}
|
||||
}
|
||||
|
||||
let status = reqwest_response.status();
|
||||
|
||||
let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
|
||||
let body = response.bytes().await.unwrap_or_else(|e| {
|
||||
warn!("server error: {}", e);
|
||||
Vec::new().into()
|
||||
}); // TODO: handle timeout
|
||||
@@ -82,7 +76,7 @@ where
|
||||
}
|
||||
|
||||
let response = T::IncomingResponse::try_from_http_response(
|
||||
http_response
|
||||
http_response_builder
|
||||
.body(body)
|
||||
.expect("reqwest body is valid http body"),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::BTreeMap, convert::TryInto};
|
||||
use std::{collections::BTreeMap, convert::TryInto, sync::Arc};
|
||||
|
||||
use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
|
||||
use log::info;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -42,7 +42,7 @@ const GUEST_NAME_LENGTH: usize = 10;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_register_available_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_username_availability::Request<'_>>,
|
||||
) -> ConduitResult<get_username_availability::Response> {
|
||||
// Validate user id
|
||||
@@ -85,10 +85,10 @@ pub async fn get_register_available_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn register_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<register::Request<'_>>,
|
||||
) -> ConduitResult<register::Response> {
|
||||
if !db.globals.allow_registration() {
|
||||
if !db.globals.allow_registration() && !body.from_appservice {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Registration has been disabled.",
|
||||
@@ -143,16 +143,28 @@ pub async fn register_route(
|
||||
|
||||
if !body.from_appservice {
|
||||
if let Some(auth) = &body.auth {
|
||||
let (worked, uiaainfo) =
|
||||
db.uiaa
|
||||
.try_auth(&user_id, "".into(), auth, &uiaainfo, &db.users, &db.globals)?;
|
||||
let (worked, uiaainfo) = db.uiaa.try_auth(
|
||||
&UserId::parse_with_server_name("", db.globals.server_name())
|
||||
.expect("we know this is valid"),
|
||||
"".into(),
|
||||
auth,
|
||||
&uiaainfo,
|
||||
&db.users,
|
||||
&db.globals,
|
||||
)?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&user_id, "".into(), &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&UserId::parse_with_server_name("", db.globals.server_name())
|
||||
.expect("we know this is valid"),
|
||||
"".into(),
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
}
|
||||
@@ -167,12 +179,11 @@ pub async fn register_route(
|
||||
let password = if is_guest {
|
||||
None
|
||||
} else {
|
||||
body.password.clone()
|
||||
}
|
||||
.unwrap_or_default();
|
||||
body.password.as_deref()
|
||||
};
|
||||
|
||||
// Create user
|
||||
db.users.create(&user_id, &password)?;
|
||||
db.users.create(&user_id, password)?;
|
||||
|
||||
// Initial data
|
||||
db.account_data.update(
|
||||
@@ -216,15 +227,25 @@ pub async fn register_route(
|
||||
)?;
|
||||
|
||||
// If this is the first user on this server, create the admins room
|
||||
if db.users.count() == 1 {
|
||||
if db.users.count()? == 1 {
|
||||
// Create a user for the server
|
||||
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
|
||||
.expect("@conduit:server_name is valid");
|
||||
|
||||
db.users.create(&conduit_user, "")?;
|
||||
db.users.create(&conduit_user, None)?;
|
||||
|
||||
let room_id = RoomId::new(db.globals.server_name());
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let mut content = ruma::events::room::create::CreateEventContent::new(conduit_user.clone());
|
||||
content.federate = true;
|
||||
content.predecessor = None;
|
||||
@@ -242,6 +263,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 2. Make conduit bot join
|
||||
@@ -263,6 +285,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 3. Power levels
|
||||
@@ -275,18 +298,8 @@ pub async fn register_route(
|
||||
event_type: EventType::RoomPowerLevels,
|
||||
content: serde_json::to_value(
|
||||
ruma::events::room::power_levels::PowerLevelsEventContent {
|
||||
ban: 50.into(),
|
||||
events: BTreeMap::new(),
|
||||
events_default: 0.into(),
|
||||
invite: 50.into(),
|
||||
kick: 50.into(),
|
||||
redact: 50.into(),
|
||||
state_default: 50.into(),
|
||||
users,
|
||||
users_default: 0.into(),
|
||||
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
|
||||
room: 50.into(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.expect("event is valid, we just created it"),
|
||||
@@ -297,6 +310,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4.1 Join Rules
|
||||
@@ -314,6 +328,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4.2 History Visibility
|
||||
@@ -333,6 +348,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4.3 Guest Access
|
||||
@@ -350,6 +366,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 6. Events implied by name and topic
|
||||
@@ -369,6 +386,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
@@ -385,6 +403,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// Room alias
|
||||
@@ -407,6 +426,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
|
||||
@@ -430,6 +450,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
@@ -449,6 +470,7 @@ pub async fn register_route(
|
||||
&user_id,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// Send welcome message
|
||||
@@ -467,6 +489,7 @@ pub async fn register_route(
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -495,7 +518,7 @@ pub async fn register_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn change_password_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<change_password::Request<'_>>,
|
||||
) -> ConduitResult<change_password::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -526,11 +549,17 @@ pub async fn change_password_route(
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&sender_user,
|
||||
&sender_device,
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
db.users.set_password(&sender_user, &body.new_password)?;
|
||||
db.users
|
||||
.set_password(&sender_user, Some(&body.new_password))?;
|
||||
|
||||
if body.logout_devices {
|
||||
// Logout all devices except the current one
|
||||
@@ -581,7 +610,7 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn deactivate_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<deactivate::Request<'_>>,
|
||||
) -> ConduitResult<deactivate::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -612,7 +641,12 @@ pub async fn deactivate_route(
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&sender_user,
|
||||
&sender_device,
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
@@ -631,6 +665,16 @@ pub async fn deactivate_route(
|
||||
third_party_invite: None,
|
||||
};
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMember,
|
||||
@@ -642,6 +686,7 @@ pub async fn deactivate_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Database, Error, Ruma};
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
api::{
|
||||
@@ -22,7 +21,7 @@ use rocket::{delete, get, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_alias_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_alias::Request<'_>>,
|
||||
) -> ConduitResult<create_alias::Response> {
|
||||
if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
|
||||
@@ -43,7 +42,7 @@ pub async fn create_alias_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_alias_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_alias::Request<'_>>,
|
||||
) -> ConduitResult<delete_alias::Response> {
|
||||
db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
|
||||
@@ -59,7 +58,7 @@ pub async fn delete_alias_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_alias_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_alias::Request<'_>>,
|
||||
) -> ConduitResult<get_alias::Response> {
|
||||
get_alias_helper(&db, &body.room_alias).await
|
||||
@@ -86,7 +85,8 @@ pub async fn get_alias_helper(
|
||||
match db.rooms.id_from_alias(&room_alias)? {
|
||||
Some(r) => room_id = Some(r),
|
||||
None => {
|
||||
for (_id, registration) in db.appservice.iter_all().filter_map(|r| r.ok()) {
|
||||
let iter = db.appservice.iter_all()?;
|
||||
for (_id, registration) in iter.filter_map(|r| r.ok()) {
|
||||
let aliases = registration
|
||||
.get("namespaces")
|
||||
.and_then(|ns| ns.get("aliases"))
|
||||
@@ -94,13 +94,7 @@ pub async fn get_alias_helper(
|
||||
.map_or_else(Vec::new, |aliases| {
|
||||
aliases
|
||||
.iter()
|
||||
.map(|aliases| {
|
||||
aliases
|
||||
.get("regex")
|
||||
.and_then(|regex| regex.as_str())
|
||||
.and_then(|regex| Regex::new(regex).ok())
|
||||
})
|
||||
.filter_map(|o| o)
|
||||
.filter_map(|aliases| Regex::new(aliases.get("regex")?.as_str()?).ok())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
|
||||
+16
-17
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
r0::backup::{
|
||||
@@ -19,7 +18,7 @@ use rocket::{delete, get, post, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_backup_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_backup::Request>,
|
||||
) -> ConduitResult<create_backup::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -38,7 +37,7 @@ pub async fn create_backup_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_backup_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<update_backup::Request<'_>>,
|
||||
) -> ConduitResult<update_backup::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -56,7 +55,7 @@ pub async fn update_backup_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_latest_backup_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_latest_backup::Request>,
|
||||
) -> ConduitResult<get_latest_backup::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -84,7 +83,7 @@ pub async fn get_latest_backup_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_backup::Request<'_>>,
|
||||
) -> ConduitResult<get_backup::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -111,7 +110,7 @@ pub async fn get_backup_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_backup::Request<'_>>,
|
||||
) -> ConduitResult<delete_backup::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -130,7 +129,7 @@ pub async fn delete_backup_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<add_backup_keys::Request<'_>>,
|
||||
) -> ConduitResult<add_backup_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -164,7 +163,7 @@ pub async fn add_backup_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<add_backup_key_sessions::Request<'_>>,
|
||||
) -> ConduitResult<add_backup_key_sessions::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -196,7 +195,7 @@ pub async fn add_backup_key_sessions_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<add_backup_key_session::Request<'_>>,
|
||||
) -> ConduitResult<add_backup_key_session::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -225,7 +224,7 @@ pub async fn add_backup_key_session_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_backup_keys::Request<'_>>,
|
||||
) -> ConduitResult<get_backup_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -241,14 +240,14 @@ pub async fn get_backup_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_backup_key_sessions::Request<'_>>,
|
||||
) -> ConduitResult<get_backup_key_sessions::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let sessions = db
|
||||
.key_backups
|
||||
.get_room(&sender_user, &body.version, &body.room_id);
|
||||
.get_room(&sender_user, &body.version, &body.room_id)?;
|
||||
|
||||
Ok(get_backup_key_sessions::Response { sessions }.into())
|
||||
}
|
||||
@@ -259,7 +258,7 @@ pub async fn get_backup_key_sessions_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_backup_key_session::Request<'_>>,
|
||||
) -> ConduitResult<get_backup_key_session::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -281,7 +280,7 @@ pub async fn get_backup_key_session_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_backup_keys::Request<'_>>,
|
||||
) -> ConduitResult<delete_backup_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -304,7 +303,7 @@ pub async fn delete_backup_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_backup_key_sessions::Request<'_>>,
|
||||
) -> ConduitResult<delete_backup_key_sessions::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -327,7 +326,7 @@ pub async fn delete_backup_key_sessions_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_backup_key_session::Request<'_>>,
|
||||
) -> ConduitResult<delete_backup_key_session::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
+42
-23
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -8,9 +7,11 @@ use ruma::{
|
||||
set_room_account_data,
|
||||
},
|
||||
},
|
||||
events::{custom::CustomEventContent, BasicEvent},
|
||||
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
|
||||
serde::Raw,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, value::RawValue as RawJsonValue};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, put};
|
||||
@@ -21,12 +22,12 @@ use rocket::{get, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_global_account_data_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_global_account_data::Request<'_>>,
|
||||
) -> ConduitResult<set_global_account_data::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let data = serde_json::from_str(body.data.get())
|
||||
let data = serde_json::from_str::<serde_json::Value>(body.data.get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
|
||||
|
||||
let event_type = body.event_type.to_string();
|
||||
@@ -35,9 +36,10 @@ pub async fn set_global_account_data_route(
|
||||
None,
|
||||
sender_user,
|
||||
event_type.clone().into(),
|
||||
&BasicEvent {
|
||||
content: CustomEventContent { event_type, data },
|
||||
},
|
||||
&json!({
|
||||
"type": event_type,
|
||||
"content": data,
|
||||
}),
|
||||
&db.globals,
|
||||
)?;
|
||||
|
||||
@@ -55,12 +57,12 @@ pub async fn set_global_account_data_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_room_account_data_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_room_account_data::Request<'_>>,
|
||||
) -> ConduitResult<set_room_account_data::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let data = serde_json::from_str(body.data.get())
|
||||
let data = serde_json::from_str::<serde_json::Value>(body.data.get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
|
||||
|
||||
let event_type = body.event_type.to_string();
|
||||
@@ -69,9 +71,10 @@ pub async fn set_room_account_data_route(
|
||||
Some(&body.room_id),
|
||||
sender_user,
|
||||
event_type.clone().into(),
|
||||
&BasicEvent {
|
||||
content: CustomEventContent { event_type, data },
|
||||
},
|
||||
&json!({
|
||||
"type": event_type,
|
||||
"content": data,
|
||||
}),
|
||||
&db.globals,
|
||||
)?;
|
||||
|
||||
@@ -86,19 +89,22 @@ pub async fn set_room_account_data_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_global_account_data_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_global_account_data::Request<'_>>,
|
||||
) -> ConduitResult<get_global_account_data::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let data = db
|
||||
let event = db
|
||||
.account_data
|
||||
.get::<Raw<ruma::events::AnyBasicEvent>>(None, sender_user, body.event_type.clone().into())?
|
||||
.get::<Box<RawJsonValue>>(None, sender_user, body.event_type.clone().into())?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(get_global_account_data::Response { account_data: data }.into())
|
||||
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
||||
.content;
|
||||
|
||||
Ok(get_global_account_data::Response { account_data }.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -110,21 +116,34 @@ pub async fn get_global_account_data_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_room_account_data_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_room_account_data::Request<'_>>,
|
||||
) -> ConduitResult<get_room_account_data::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let data = db
|
||||
let event = db
|
||||
.account_data
|
||||
.get::<Raw<ruma::events::AnyBasicEvent>>(
|
||||
.get::<Box<RawJsonValue>>(
|
||||
Some(&body.room_id),
|
||||
sender_user,
|
||||
body.event_type.clone().into(),
|
||||
)?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(get_room_account_data::Response { account_data: data }.into())
|
||||
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
||||
.content;
|
||||
|
||||
Ok(get_room_account_data::Response { account_data }.into())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractRoomEventContent {
|
||||
content: Raw<AnyRoomAccountDataEventContent>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractGlobalEventContent {
|
||||
content: Raw<AnyGlobalAccountDataEventContent>,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::api::client::{error::ErrorKind, r0::context::get_context};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
@@ -12,7 +11,7 @@ use rocket::get;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_context_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_context::Request<'_>>,
|
||||
) -> ConduitResult<get_context::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{utils, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, utils, ConduitResult, Error, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
@@ -18,7 +17,7 @@ use rocket::{delete, get, post, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_devices_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_devices::Request>,
|
||||
) -> ConduitResult<get_devices::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -38,7 +37,7 @@ pub async fn get_devices_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_device_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_device::Request<'_>>,
|
||||
) -> ConduitResult<get_device::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -57,7 +56,7 @@ pub async fn get_device_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_device_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<update_device::Request<'_>>,
|
||||
) -> ConduitResult<update_device::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -83,7 +82,7 @@ pub async fn update_device_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_device_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_device::Request<'_>>,
|
||||
) -> ConduitResult<delete_device::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -115,7 +114,12 @@ pub async fn delete_device_route(
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&sender_user,
|
||||
&sender_device,
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
@@ -132,7 +136,7 @@ pub async fn delete_device_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_devices_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_devices::Request<'_>>,
|
||||
) -> ConduitResult<delete_devices::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -164,7 +168,12 @@ pub async fn delete_devices_route(
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&sender_user,
|
||||
&sender_device,
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Result, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Database, Error, Result, Ruma};
|
||||
use log::info;
|
||||
use ruma::{
|
||||
api::{
|
||||
@@ -33,7 +32,7 @@ use rocket::{get, post, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_public_rooms_filtered_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_public_rooms_filtered::Request<'_>>,
|
||||
) -> ConduitResult<get_public_rooms_filtered::Response> {
|
||||
get_public_rooms_filtered_helper(
|
||||
@@ -53,7 +52,7 @@ pub async fn get_public_rooms_filtered_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_public_rooms_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_public_rooms::Request<'_>>,
|
||||
) -> ConduitResult<get_public_rooms::Response> {
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
@@ -82,7 +81,7 @@ pub async fn get_public_rooms_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_room_visibility_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_room_visibility::Request<'_>>,
|
||||
) -> ConduitResult<set_room_visibility::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -112,7 +111,7 @@ pub async fn set_room_visibility_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_room_visibility_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_room_visibility::Request<'_>>,
|
||||
) -> ConduitResult<get_room_visibility::Response> {
|
||||
Ok(get_room_visibility::Response {
|
||||
@@ -133,9 +132,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
filter: &IncomingFilter,
|
||||
_network: &IncomingRoomNetwork,
|
||||
) -> ConduitResult<get_public_rooms_filtered::Response> {
|
||||
if let Some(other_server) = server
|
||||
.clone()
|
||||
.filter(|server| *server != db.globals.server_name().as_str())
|
||||
if let Some(other_server) = server.filter(|server| *server != db.globals.server_name().as_str())
|
||||
{
|
||||
let response = db
|
||||
.sending
|
||||
@@ -160,15 +157,12 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map(|c| {
|
||||
// Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
|
||||
// to ruma::api::client::r0::directory::PublicRoomsChunk
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_str(
|
||||
&serde_json::to_string(&c)
|
||||
.expect("PublicRoomsChunk::to_string always works"),
|
||||
)
|
||||
.expect("federation and client-server PublicRoomsChunk are the same type"),
|
||||
serde_json::from_str(
|
||||
&serde_json::to_string(&c)
|
||||
.expect("PublicRoomsChunk::to_string always works"),
|
||||
)
|
||||
.expect("federation and client-server PublicRoomsChunk are the same type")
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect(),
|
||||
prev_batch: response.prev_batch,
|
||||
next_batch: response.next_batch,
|
||||
@@ -217,7 +211,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map_or(Ok::<_, Error>(None), |s| {
|
||||
Ok(serde_json::from_value::<
|
||||
Raw<canonical_alias::CanonicalAliasEventContent>,
|
||||
>(s.content)
|
||||
>(s.content.clone())
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
@@ -230,7 +224,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.room_state_get(&room_id, &EventType::RoomName, "")?
|
||||
.map_or(Ok::<_, Error>(None), |s| {
|
||||
Ok(serde_json::from_value::<Raw<name::NameEventContent>>(
|
||||
s.content,
|
||||
s.content.clone(),
|
||||
)
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
@@ -247,7 +241,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map_or(Ok::<_, Error>(None), |s| {
|
||||
Ok(Some(
|
||||
serde_json::from_value::<Raw<topic::TopicEventContent>>(
|
||||
s.content,
|
||||
s.content.clone(),
|
||||
)
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
@@ -263,7 +257,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map_or(Ok::<_, Error>(false), |s| {
|
||||
Ok(serde_json::from_value::<
|
||||
Raw<history_visibility::HistoryVisibilityEventContent>,
|
||||
>(s.content)
|
||||
>(s.content.clone())
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
@@ -280,7 +274,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map_or(Ok::<_, Error>(false), |s| {
|
||||
Ok(
|
||||
serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>(
|
||||
s.content,
|
||||
s.content.clone(),
|
||||
)
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
@@ -297,7 +291,7 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
.map(|s| {
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_value::<Raw<avatar::AvatarEventContent>>(
|
||||
s.content,
|
||||
s.content.clone(),
|
||||
)
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
@@ -315,6 +309,36 @@ pub async fn get_public_rooms_filtered_helper(
|
||||
Ok(chunk)
|
||||
})
|
||||
.filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
|
||||
.filter(|chunk| {
|
||||
if let Some(query) = filter
|
||||
.generic_search_term
|
||||
.as_ref()
|
||||
.map(|q| q.to_lowercase())
|
||||
{
|
||||
if let Some(name) = &chunk.name {
|
||||
if name.to_lowercase().contains(&query) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(topic) = &chunk.topic {
|
||||
if topic.to_lowercase().contains(&query) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(canonical_alias) = &chunk.canonical_alias {
|
||||
if canonical_alias.as_str().to_lowercase().contains(&query) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
// No search term
|
||||
true
|
||||
}
|
||||
})
|
||||
// We need to collect all, so we can sort by member count
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
+128
-96
@@ -1,5 +1,5 @@
|
||||
use super::{State, SESSION_ID_LENGTH};
|
||||
use crate::{utils, ConduitResult, Database, Error, Ruma};
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::{database::DatabaseGuard, utils, ConduitResult, Database, Error, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -12,6 +12,7 @@ use ruma::{
|
||||
},
|
||||
},
|
||||
encryption::UnsignedDeviceInfo,
|
||||
DeviceId, DeviceKeyAlgorithm, UserId,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
@@ -24,7 +25,7 @@ use rocket::{get, post};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<upload_keys::Request>,
|
||||
) -> ConduitResult<upload_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -73,79 +74,19 @@ pub async fn upload_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_keys::Request<'_>>,
|
||||
) -> ConduitResult<get_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let mut master_keys = BTreeMap::new();
|
||||
let mut self_signing_keys = BTreeMap::new();
|
||||
let mut user_signing_keys = BTreeMap::new();
|
||||
let mut device_keys = BTreeMap::new();
|
||||
let response = get_keys_helper(
|
||||
Some(sender_user),
|
||||
&body.device_keys,
|
||||
|u| u == sender_user,
|
||||
&db,
|
||||
)?;
|
||||
|
||||
for (user_id, device_ids) in &body.device_keys {
|
||||
if device_ids.is_empty() {
|
||||
let mut container = BTreeMap::new();
|
||||
for device_id in db.users.all_device_ids(user_id) {
|
||||
let device_id = device_id?;
|
||||
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
|
||||
let metadata = db
|
||||
.users
|
||||
.get_device_metadata(user_id, &device_id)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("all_device_keys contained nonexistent device.")
|
||||
})?;
|
||||
|
||||
keys.unsigned = UnsignedDeviceInfo {
|
||||
device_display_name: metadata.display_name,
|
||||
};
|
||||
|
||||
container.insert(device_id, keys);
|
||||
}
|
||||
}
|
||||
device_keys.insert(user_id.clone(), container);
|
||||
} else {
|
||||
for device_id in device_ids {
|
||||
let mut container = BTreeMap::new();
|
||||
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
|
||||
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to get keys for nonexistent device.",
|
||||
),
|
||||
)?;
|
||||
|
||||
keys.unsigned = UnsignedDeviceInfo {
|
||||
device_display_name: metadata.display_name,
|
||||
};
|
||||
|
||||
container.insert(device_id.clone(), keys);
|
||||
}
|
||||
device_keys.insert(user_id.clone(), container);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(master_key) = db.users.get_master_key(user_id, sender_user)? {
|
||||
master_keys.insert(user_id.clone(), master_key);
|
||||
}
|
||||
if let Some(self_signing_key) = db.users.get_self_signing_key(user_id, sender_user)? {
|
||||
self_signing_keys.insert(user_id.clone(), self_signing_key);
|
||||
}
|
||||
if user_id == sender_user {
|
||||
if let Some(user_signing_key) = db.users.get_user_signing_key(sender_user)? {
|
||||
user_signing_keys.insert(user_id.clone(), user_signing_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(get_keys::Response {
|
||||
master_keys,
|
||||
self_signing_keys,
|
||||
user_signing_keys,
|
||||
device_keys,
|
||||
failures: BTreeMap::new(),
|
||||
}
|
||||
.into())
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -154,32 +95,14 @@ pub async fn get_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn claim_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<claim_keys::Request>,
|
||||
) -> ConduitResult<claim_keys::Response> {
|
||||
let mut one_time_keys = BTreeMap::new();
|
||||
for (user_id, map) in &body.one_time_keys {
|
||||
let mut container = BTreeMap::new();
|
||||
for (device_id, key_algorithm) in map {
|
||||
if let Some(one_time_keys) =
|
||||
db.users
|
||||
.take_one_time_key(user_id, device_id, key_algorithm, &db.globals)?
|
||||
{
|
||||
let mut c = BTreeMap::new();
|
||||
c.insert(one_time_keys.0, one_time_keys.1);
|
||||
container.insert(device_id.clone(), c);
|
||||
}
|
||||
}
|
||||
one_time_keys.insert(user_id.clone(), container);
|
||||
}
|
||||
let response = claim_keys_helper(&body.one_time_keys, &db)?;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(claim_keys::Response {
|
||||
failures: BTreeMap::new(),
|
||||
one_time_keys,
|
||||
}
|
||||
.into())
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -188,7 +111,7 @@ pub async fn claim_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_signing_keys_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<upload_signing_keys::Request<'_>>,
|
||||
) -> ConduitResult<upload_signing_keys::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -220,7 +143,12 @@ pub async fn upload_signing_keys_route(
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
|
||||
db.uiaa.create(
|
||||
&sender_user,
|
||||
&sender_device,
|
||||
&uiaainfo,
|
||||
&body.json_body.expect("body is json"),
|
||||
)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
@@ -246,7 +174,7 @@ pub async fn upload_signing_keys_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_signatures_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<upload_signatures::Request>,
|
||||
) -> ConduitResult<upload_signatures::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -307,7 +235,7 @@ pub async fn upload_signatures_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_key_changes_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_key_changes::Request<'_>>,
|
||||
) -> ConduitResult<get_key_changes::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -351,3 +279,107 @@ pub async fn get_key_changes_route(
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
||||
sender_user: Option<&UserId>,
|
||||
device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>,
|
||||
allowed_signatures: F,
|
||||
db: &Database,
|
||||
) -> Result<get_keys::Response> {
|
||||
let mut master_keys = BTreeMap::new();
|
||||
let mut self_signing_keys = BTreeMap::new();
|
||||
let mut user_signing_keys = BTreeMap::new();
|
||||
let mut device_keys = BTreeMap::new();
|
||||
|
||||
for (user_id, device_ids) in device_keys_input {
|
||||
if device_ids.is_empty() {
|
||||
let mut container = BTreeMap::new();
|
||||
for device_id in db.users.all_device_ids(user_id) {
|
||||
let device_id = device_id?;
|
||||
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
|
||||
let metadata = db
|
||||
.users
|
||||
.get_device_metadata(user_id, &device_id)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("all_device_keys contained nonexistent device.")
|
||||
})?;
|
||||
|
||||
keys.unsigned = UnsignedDeviceInfo {
|
||||
device_display_name: metadata.display_name,
|
||||
};
|
||||
|
||||
container.insert(device_id, keys);
|
||||
}
|
||||
}
|
||||
device_keys.insert(user_id.clone(), container);
|
||||
} else {
|
||||
for device_id in device_ids {
|
||||
let mut container = BTreeMap::new();
|
||||
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
|
||||
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to get keys for nonexistent device.",
|
||||
),
|
||||
)?;
|
||||
|
||||
keys.unsigned = UnsignedDeviceInfo {
|
||||
device_display_name: metadata.display_name,
|
||||
};
|
||||
|
||||
container.insert(device_id.clone(), keys);
|
||||
}
|
||||
device_keys.insert(user_id.clone(), container);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(master_key) = db.users.get_master_key(user_id, &allowed_signatures)? {
|
||||
master_keys.insert(user_id.clone(), master_key);
|
||||
}
|
||||
if let Some(self_signing_key) = db
|
||||
.users
|
||||
.get_self_signing_key(user_id, &allowed_signatures)?
|
||||
{
|
||||
self_signing_keys.insert(user_id.clone(), self_signing_key);
|
||||
}
|
||||
if Some(user_id) == sender_user {
|
||||
if let Some(user_signing_key) = db.users.get_user_signing_key(user_id)? {
|
||||
user_signing_keys.insert(user_id.clone(), user_signing_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(get_keys::Response {
|
||||
master_keys,
|
||||
self_signing_keys,
|
||||
user_signing_keys,
|
||||
device_keys,
|
||||
failures: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn claim_keys_helper(
|
||||
one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
|
||||
db: &Database,
|
||||
) -> Result<claim_keys::Response> {
|
||||
let mut one_time_keys = BTreeMap::new();
|
||||
for (user_id, map) in one_time_keys_input {
|
||||
let mut container = BTreeMap::new();
|
||||
for (device_id, key_algorithm) in map {
|
||||
if let Some(one_time_keys) =
|
||||
db.users
|
||||
.take_one_time_key(user_id, device_id, key_algorithm, &db.globals)?
|
||||
{
|
||||
let mut c = BTreeMap::new();
|
||||
c.insert(one_time_keys.0, one_time_keys.1);
|
||||
container.insert(device_id.clone(), c);
|
||||
}
|
||||
}
|
||||
one_time_keys.insert(user_id.clone(), container);
|
||||
}
|
||||
|
||||
Ok(claim_keys::Response {
|
||||
failures: BTreeMap::new(),
|
||||
one_time_keys,
|
||||
})
|
||||
}
|
||||
|
||||
+59
-39
@@ -1,20 +1,21 @@
|
||||
use super::State;
|
||||
use crate::{database::media::FileMeta, utils, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{
|
||||
database::media::FileMeta, database::DatabaseGuard, utils, ConduitResult, Error, Ruma,
|
||||
};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
r0::media::{create_content, get_content, get_content_thumbnail, get_media_config},
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post};
|
||||
use std::convert::TryInto;
|
||||
|
||||
const MXC_LENGTH: usize = 32;
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))]
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub async fn get_media_config_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
) -> ConduitResult<get_media_config::Response> {
|
||||
Ok(get_media_config::Response {
|
||||
upload_size: db.globals.max_request_size().into(),
|
||||
@@ -28,7 +29,7 @@ pub async fn get_media_config_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_content_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_content::Request<'_>>,
|
||||
) -> ConduitResult<create_content::Response> {
|
||||
let mxc = format!(
|
||||
@@ -36,12 +37,20 @@ pub async fn create_content_route(
|
||||
db.globals.server_name(),
|
||||
utils::random_string(MXC_LENGTH)
|
||||
);
|
||||
db.media.create(
|
||||
mxc.clone(),
|
||||
&body.filename.as_deref(),
|
||||
&body.content_type.as_deref(),
|
||||
&body.file,
|
||||
)?;
|
||||
|
||||
db.media
|
||||
.create(
|
||||
mxc.clone(),
|
||||
&db.globals,
|
||||
&body
|
||||
.filename
|
||||
.as_ref()
|
||||
.map(|filename| "inline; filename=".to_owned() + filename)
|
||||
.as_deref(),
|
||||
&body.content_type.as_deref(),
|
||||
&body.file,
|
||||
)
|
||||
.await?;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
@@ -58,21 +67,21 @@ pub async fn create_content_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_content_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_content::Request<'_>>,
|
||||
) -> ConduitResult<get_content::Response> {
|
||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||
|
||||
if let Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file,
|
||||
}) = db.media.get(&mxc)?
|
||||
}) = db.media.get(&db.globals, &mxc).await?
|
||||
{
|
||||
Ok(get_content::Response {
|
||||
file,
|
||||
content_type,
|
||||
content_disposition: filename,
|
||||
content_disposition,
|
||||
}
|
||||
.into())
|
||||
} else if &*body.server_name != db.globals.server_name() && body.allow_remote {
|
||||
@@ -89,12 +98,15 @@ pub async fn get_content_route(
|
||||
)
|
||||
.await?;
|
||||
|
||||
db.media.create(
|
||||
mxc,
|
||||
&get_content_response.content_disposition.as_deref(),
|
||||
&get_content_response.content_type.as_deref(),
|
||||
&get_content_response.file,
|
||||
)?;
|
||||
db.media
|
||||
.create(
|
||||
mxc,
|
||||
&db.globals,
|
||||
&get_content_response.content_disposition.as_deref(),
|
||||
&get_content_response.content_type.as_deref(),
|
||||
&get_content_response.file,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(get_content_response.into())
|
||||
} else {
|
||||
@@ -108,22 +120,27 @@ pub async fn get_content_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_content_thumbnail_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_content_thumbnail::Request<'_>>,
|
||||
) -> ConduitResult<get_content_thumbnail::Response> {
|
||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||
|
||||
if let Some(FileMeta {
|
||||
content_type, file, ..
|
||||
}) = db.media.get_thumbnail(
|
||||
mxc.clone(),
|
||||
body.width
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||
body.height
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||
)? {
|
||||
}) = db
|
||||
.media
|
||||
.get_thumbnail(
|
||||
mxc.clone(),
|
||||
&db.globals,
|
||||
body.width
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||
body.height
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Ok(get_content_thumbnail::Response { file, content_type }.into())
|
||||
} else if &*body.server_name != db.globals.server_name() && body.allow_remote {
|
||||
let get_thumbnail_response = db
|
||||
@@ -142,14 +159,17 @@ pub async fn get_content_thumbnail_route(
|
||||
)
|
||||
.await?;
|
||||
|
||||
db.media.upload_thumbnail(
|
||||
mxc,
|
||||
&None,
|
||||
&get_thumbnail_response.content_type,
|
||||
body.width.try_into().expect("all UInts are valid u32s"),
|
||||
body.height.try_into().expect("all UInts are valid u32s"),
|
||||
&get_thumbnail_response.file,
|
||||
)?;
|
||||
db.media
|
||||
.upload_thumbnail(
|
||||
mxc,
|
||||
&db.globals,
|
||||
&None,
|
||||
&get_thumbnail_response.content_type,
|
||||
body.width.try_into().expect("all UInts are valid u32s"),
|
||||
body.height.try_into().expect("all UInts are valid u32s"),
|
||||
&get_thumbnail_response.file,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(get_thumbnail_response.into())
|
||||
} else {
|
||||
|
||||
+149
-89
@@ -1,10 +1,10 @@
|
||||
use super::State;
|
||||
use crate::{
|
||||
client_server,
|
||||
database::DatabaseGuard,
|
||||
pdu::{PduBuilder, PduEvent},
|
||||
server_server, utils, ConduitResult, Database, Error, Result, Ruma,
|
||||
};
|
||||
use log::{error, warn};
|
||||
use log::{debug, error, warn};
|
||||
use member::{MemberEventContent, MembershipState};
|
||||
use rocket::futures;
|
||||
use ruma::{
|
||||
@@ -25,13 +25,14 @@ use ruma::{
|
||||
EventType,
|
||||
},
|
||||
serde::{to_canonical_value, CanonicalJsonObject, CanonicalJsonValue, Raw},
|
||||
state_res::{self, RoomVersion},
|
||||
uint, EventId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use state_res::EventMap;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
collections::{btree_map::Entry, BTreeMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::{Arc, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -43,7 +44,7 @@ use rocket::{get, post};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn join_room_by_id_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<join_room_by_id::Request<'_>>,
|
||||
) -> ConduitResult<join_room_by_id::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -64,14 +65,18 @@ pub async fn join_room_by_id_route(
|
||||
|
||||
servers.insert(body.room_id.server_name().to_owned());
|
||||
|
||||
join_room_by_id_helper(
|
||||
let ret = join_room_by_id_helper(
|
||||
&db,
|
||||
body.sender_user.as_ref(),
|
||||
&body.room_id,
|
||||
&servers,
|
||||
body.third_party_signed.as_ref(),
|
||||
)
|
||||
.await
|
||||
.await;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -80,7 +85,7 @@ pub async fn join_room_by_id_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn join_room_by_id_or_alias_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<join_room_by_id_or_alias::Request<'_>>,
|
||||
) -> ConduitResult<join_room_by_id_or_alias::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -134,7 +139,7 @@ pub async fn join_room_by_id_or_alias_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn leave_room_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<leave_room::Request<'_>>,
|
||||
) -> ConduitResult<leave_room::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -152,7 +157,7 @@ pub async fn leave_room_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn invite_user_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<invite_user::Request<'_>>,
|
||||
) -> ConduitResult<invite_user::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -172,7 +177,7 @@ pub async fn invite_user_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn kick_user_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<kick_user::Request<'_>>,
|
||||
) -> ConduitResult<kick_user::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -188,7 +193,8 @@ pub async fn kick_user_route(
|
||||
ErrorKind::BadState,
|
||||
"Cannot kick member that's not in the room.",
|
||||
))?
|
||||
.content,
|
||||
.content
|
||||
.clone(),
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
@@ -197,6 +203,16 @@ pub async fn kick_user_route(
|
||||
event.membership = ruma::events::room::member::MembershipState::Leave;
|
||||
// TODO: reason
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMember,
|
||||
@@ -208,8 +224,11 @@ pub async fn kick_user_route(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(kick_user::Response::new().into())
|
||||
@@ -221,7 +240,7 @@ pub async fn kick_user_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn ban_user_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<ban_user::Request<'_>>,
|
||||
) -> ConduitResult<ban_user::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -244,16 +263,27 @@ pub async fn ban_user_route(
|
||||
third_party_invite: None,
|
||||
}),
|
||||
|event| {
|
||||
let mut event =
|
||||
serde_json::from_value::<Raw<member::MemberEventContent>>(event.content)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
|
||||
let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
|
||||
event.content.clone(),
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
|
||||
event.membership = ruma::events::room::member::MembershipState::Ban;
|
||||
Ok(event)
|
||||
},
|
||||
)?;
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMember,
|
||||
@@ -265,8 +295,11 @@ pub async fn ban_user_route(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(ban_user::Response::new().into())
|
||||
@@ -278,7 +311,7 @@ pub async fn ban_user_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn unban_user_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<unban_user::Request<'_>>,
|
||||
) -> ConduitResult<unban_user::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -294,7 +327,8 @@ pub async fn unban_user_route(
|
||||
ErrorKind::BadState,
|
||||
"Cannot unban a user who is not banned.",
|
||||
))?
|
||||
.content,
|
||||
.content
|
||||
.clone(),
|
||||
)
|
||||
.expect("from_value::<Raw<..>> can never fail")
|
||||
.deserialize()
|
||||
@@ -302,6 +336,16 @@ pub async fn unban_user_route(
|
||||
|
||||
event.membership = ruma::events::room::member::MembershipState::Leave;
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMember,
|
||||
@@ -313,8 +357,11 @@ pub async fn unban_user_route(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(unban_user::Response::new().into())
|
||||
@@ -326,7 +373,7 @@ pub async fn unban_user_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn forget_room_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<forget_room::Request<'_>>,
|
||||
) -> ConduitResult<forget_room::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -344,7 +391,7 @@ pub async fn forget_room_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn joined_rooms_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<joined_rooms::Request>,
|
||||
) -> ConduitResult<joined_rooms::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -365,7 +412,7 @@ pub async fn joined_rooms_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_member_events_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_member_events::Request<'_>>,
|
||||
) -> ConduitResult<get_member_events::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -395,7 +442,7 @@ pub async fn get_member_events_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn joined_members_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<joined_members::Request<'_>>,
|
||||
) -> ConduitResult<joined_members::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -438,6 +485,16 @@ async fn join_room_by_id_helper(
|
||||
) -> ConduitResult<join_room_by_id::Response> {
|
||||
let sender_user = sender_user.expect("user is authenticated");
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
// Ask a remote server if we don't have this room
|
||||
if !db.rooms.exists(&room_id)? && room_id.server_name() != db.globals.server_name() {
|
||||
let mut make_join_response_and_server = Err(Error::BadServerResponse(
|
||||
@@ -545,12 +602,6 @@ async fn join_room_by_id_helper(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count = db.globals.next_count()?;
|
||||
|
||||
let mut pdu_id = room_id.as_bytes().to_vec();
|
||||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&count.to_be_bytes());
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, join_event.clone())
|
||||
.map_err(|_| Error::BadServerResponse("Invalid join event PDU."))?;
|
||||
|
||||
@@ -578,36 +629,6 @@ async fn join_room_by_id_helper(
|
||||
|
||||
db.rooms.add_pdu_outlier(&event_id, &value)?;
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
if pdu.kind == EventType::RoomMember {
|
||||
let target_user_id = UserId::try_from(state_key.clone()).map_err(|e| {
|
||||
warn!(
|
||||
"Invalid user id in send_join response: {}: {}",
|
||||
state_key, e
|
||||
);
|
||||
Error::BadServerResponse("Invalid user id in send_join response.")
|
||||
})?;
|
||||
|
||||
let invite_state = Vec::new(); // TODO add a few important events
|
||||
|
||||
// Update our membership info, we do this here incase a user is invited
|
||||
// and immediately leaves we need the DB to record the invite event for auth
|
||||
db.rooms.update_membership(
|
||||
&pdu.room_id,
|
||||
&target_user_id,
|
||||
serde_json::from_value::<member::MembershipState>(
|
||||
pdu.content
|
||||
.get("membership")
|
||||
.ok_or(Error::BadServerResponse("Invalid member event content"))?
|
||||
.clone(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::BadServerResponse("Invalid membership state content.")
|
||||
})?,
|
||||
&pdu.sender,
|
||||
Some(invite_state),
|
||||
db,
|
||||
)?;
|
||||
}
|
||||
state.insert((pdu.kind.clone(), state_key.clone()), pdu.event_id.clone());
|
||||
}
|
||||
}
|
||||
@@ -650,8 +671,6 @@ async fn join_room_by_id_helper(
|
||||
db.rooms.append_pdu(
|
||||
&pdu,
|
||||
utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"),
|
||||
db.globals.next_count()?,
|
||||
pdu_id.into(),
|
||||
&[pdu.event_id.clone()],
|
||||
db,
|
||||
)?;
|
||||
@@ -679,9 +698,12 @@ async fn join_room_by_id_helper(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(join_room_by_id::Response::new(room_id.clone()).into())
|
||||
@@ -694,9 +716,41 @@ async fn validate_and_add_event_id(
|
||||
db: &Database,
|
||||
) -> Result<(EventId, CanonicalJsonObject)> {
|
||||
let mut value = serde_json::from_str::<CanonicalJsonObject>(pdu.json().get()).map_err(|e| {
|
||||
error!("{:?}: {:?}", pdu, e);
|
||||
error!("Invalid PDU in server response: {:?}: {:?}", pdu, e);
|
||||
Error::BadServerResponse("Invalid PDU in server response")
|
||||
})?;
|
||||
let event_id = EventId::try_from(&*format!(
|
||||
"${}",
|
||||
ruma::signatures::reference_hash(&value, &room_version)
|
||||
.expect("ruma can calculate reference hashes")
|
||||
))
|
||||
.expect("ruma's reference hashes are valid event ids");
|
||||
|
||||
let back_off = |id| match db.globals.bad_event_ratelimiter.write().unwrap().entry(id) {
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
}
|
||||
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
};
|
||||
|
||||
if let Some((time, tries)) = db
|
||||
.globals
|
||||
.bad_event_ratelimiter
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&event_id)
|
||||
{
|
||||
// Exponential backoff
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
debug!("Backing off from {}", event_id);
|
||||
return Err(Error::BadServerResponse("bad event, still backing off"));
|
||||
}
|
||||
}
|
||||
|
||||
server_server::fetch_required_signing_keys(&value, pub_key_map, db).await?;
|
||||
if let Err(e) = ruma::signatures::verify_event(
|
||||
@@ -706,17 +760,11 @@ async fn validate_and_add_event_id(
|
||||
&value,
|
||||
room_version,
|
||||
) {
|
||||
warn!("Event failed verification: {}", e);
|
||||
warn!("Event {} failed verification {:?} {}", event_id, pdu, e);
|
||||
back_off(event_id);
|
||||
return Err(Error::BadServerResponse("Event failed verification."));
|
||||
}
|
||||
|
||||
let event_id = EventId::try_from(&*format!(
|
||||
"${}",
|
||||
ruma::signatures::reference_hash(&value, &room_version)
|
||||
.expect("ruma can calculate reference hashes")
|
||||
))
|
||||
.expect("ruma's reference hashes are valid event ids");
|
||||
|
||||
value.insert(
|
||||
"event_id".to_owned(),
|
||||
CanonicalJsonValue::String(event_id.as_str().to_owned()),
|
||||
@@ -725,13 +773,23 @@ async fn validate_and_add_event_id(
|
||||
Ok((event_id, value))
|
||||
}
|
||||
|
||||
pub async fn invite_helper(
|
||||
pub async fn invite_helper<'a>(
|
||||
sender_user: &UserId,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
db: &Database,
|
||||
is_direct: bool,
|
||||
) -> Result<()> {
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
if user_id.server_name() != db.globals.server_name() {
|
||||
let prev_events = db
|
||||
.rooms
|
||||
@@ -747,27 +805,27 @@ pub async fn invite_helper(
|
||||
let create_event_content = create_event
|
||||
.as_ref()
|
||||
.map(|create_event| {
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone())
|
||||
.expect("Raw::from_value always works.")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))?,
|
||||
)
|
||||
serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone())
|
||||
.expect("Raw::from_value always works.")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let create_prev_event = if prev_events.len() == 1
|
||||
&& Some(&prev_events[0]) == create_event.as_ref().map(|c| &c.event_id)
|
||||
{
|
||||
create_event.map(Arc::new)
|
||||
create_event
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// If there was no create event yet, assume we are creating a version 6 room right now
|
||||
let room_version = create_event_content.map_or(RoomVersionId::Version6, |create_event| {
|
||||
create_event.room_version
|
||||
});
|
||||
let room_version_id = create_event_content
|
||||
.map_or(RoomVersionId::Version6, |create_event| {
|
||||
create_event.room_version
|
||||
});
|
||||
let room_version = RoomVersion::new(&room_version_id).expect("room version is supported");
|
||||
|
||||
let content = serde_json::to_value(MemberEventContent {
|
||||
avatar_url: None,
|
||||
@@ -796,10 +854,10 @@ pub async fn invite_helper(
|
||||
let mut unsigned = BTreeMap::new();
|
||||
|
||||
if let Some(prev_pdu) = db.rooms.room_state_get(room_id, &kind, &state_key)? {
|
||||
unsigned.insert("prev_content".to_owned(), prev_pdu.content);
|
||||
unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
|
||||
unsigned.insert(
|
||||
"prev_sender".to_owned(),
|
||||
serde_json::to_value(prev_pdu.sender).expect("UserId::to_value always works"),
|
||||
serde_json::to_value(&prev_pdu.sender).expect("UserId::to_value always works"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -841,7 +899,7 @@ pub async fn invite_helper(
|
||||
|
||||
if !auth_check {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
ErrorKind::Forbidden,
|
||||
"Event is not authorized.",
|
||||
));
|
||||
}
|
||||
@@ -863,10 +921,12 @@ pub async fn invite_helper(
|
||||
db.globals.server_name().as_str(),
|
||||
db.globals.keypair(),
|
||||
&mut pdu_json,
|
||||
&room_version,
|
||||
&room_version_id,
|
||||
)
|
||||
.expect("event is valid, we just created it");
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
let invite_room_state = db.rooms.calculate_invite_state(&pdu)?;
|
||||
let response = db
|
||||
.sending
|
||||
@@ -884,7 +944,6 @@ pub async fn invite_helper(
|
||||
.await?;
|
||||
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
let mut auth_cache = EventMap::new();
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||
let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event) {
|
||||
@@ -910,11 +969,11 @@ pub async fn invite_helper(
|
||||
let pdu_id = server_server::handle_incoming_pdu(
|
||||
&origin,
|
||||
&event_id,
|
||||
&room_id,
|
||||
value,
|
||||
true,
|
||||
&db,
|
||||
&pub_key_map,
|
||||
&mut auth_cache,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
@@ -947,7 +1006,7 @@ pub async fn invite_helper(
|
||||
membership: member::MembershipState::Invite,
|
||||
displayname: db.users.displayname(&user_id)?,
|
||||
avatar_url: db.users.avatar_url(&user_id)?,
|
||||
is_direct: None,
|
||||
is_direct: Some(is_direct),
|
||||
third_party_invite: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
@@ -958,6 +1017,7 @@ pub async fn invite_helper(
|
||||
&sender_user,
|
||||
room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -11,6 +10,7 @@ use ruma::{
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -22,12 +22,22 @@ use rocket::{get, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_message_event_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<send_message_event::Request<'_>>,
|
||||
) -> ConduitResult<send_message_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_deref();
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
// Check if this is a new transaction id
|
||||
if let Some(response) =
|
||||
db.transaction_ids
|
||||
@@ -56,13 +66,8 @@ pub async fn send_message_event_route(
|
||||
let event_id = db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::from(&body.event_type),
|
||||
content: serde_json::from_str(
|
||||
body.json_body
|
||||
.as_ref()
|
||||
.ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?
|
||||
.get(),
|
||||
)
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
|
||||
content: serde_json::from_str(body.body.body.json().get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
|
||||
unsigned: Some(unsigned),
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
@@ -70,6 +75,7 @@ pub async fn send_message_event_route(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
db.transaction_ids.add_txnid(
|
||||
@@ -79,6 +85,8 @@ pub async fn send_message_event_route(
|
||||
event_id.as_bytes(),
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(send_message_event::Response::new(event_id).into())
|
||||
@@ -90,7 +98,7 @@ pub async fn send_message_event_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_message_events_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_message_events::Request<'_>>,
|
||||
) -> ConduitResult<get_message_events::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -64,14 +64,12 @@ pub use voip::*;
|
||||
use super::State;
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use {
|
||||
crate::ConduitResult,
|
||||
rocket::{options, State},
|
||||
ruma::api::client::r0::to_device::send_event_to_device,
|
||||
crate::ConduitResult, rocket::options, ruma::api::client::r0::to_device::send_event_to_device,
|
||||
};
|
||||
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
const TOKEN_LENGTH: usize = 256;
|
||||
const SESSION_ID_LENGTH: usize = 256;
|
||||
pub const DEVICE_ID_LENGTH: usize = 10;
|
||||
pub const TOKEN_LENGTH: usize = 256;
|
||||
pub const SESSION_ID_LENGTH: usize = 256;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
#[options("/<_..>")]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use super::State;
|
||||
use crate::{utils, ConduitResult, Database, Ruma};
|
||||
use ruma::api::client::r0::presence::set_presence;
|
||||
use std::convert::TryInto;
|
||||
use crate::{database::DatabaseGuard, utils, ConduitResult, Ruma};
|
||||
use ruma::api::client::r0::presence::{get_presence, set_presence};
|
||||
use std::{convert::TryInto, time::Duration};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::put;
|
||||
use rocket::{get, put};
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
@@ -12,7 +11,7 @@ use rocket::put;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_presence_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_presence::Request<'_>>,
|
||||
) -> ConduitResult<set_presence::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -46,3 +45,48 @@ pub async fn set_presence_route(
|
||||
|
||||
Ok(set_presence::Response.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/presence/<_>/status", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_presence_route(
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_presence::Request<'_>>,
|
||||
) -> ConduitResult<get_presence::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let mut presence_event = None;
|
||||
|
||||
for room_id in db
|
||||
.rooms
|
||||
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
|
||||
{
|
||||
let room_id = room_id?;
|
||||
|
||||
if let Some(presence) = db
|
||||
.rooms
|
||||
.edus
|
||||
.get_last_presence_event(&sender_user, &room_id)?
|
||||
{
|
||||
presence_event = Some(presence);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(presence) = presence_event {
|
||||
Ok(get_presence::Response {
|
||||
// TODO: Should ruma just use the presenceeventcontent type here?
|
||||
status_msg: presence.content.status_msg,
|
||||
currently_active: presence.content.currently_active,
|
||||
last_active_ago: presence
|
||||
.content
|
||||
.last_active_ago
|
||||
.map(|millis| Duration::from_millis(millis.into())),
|
||||
presence: presence.content.presence,
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -10,10 +9,10 @@ use ruma::{
|
||||
events::EventType,
|
||||
serde::Raw,
|
||||
};
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, put};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
@@ -21,7 +20,7 @@ use std::convert::TryInto;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_displayname_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_display_name::Request<'_>>,
|
||||
) -> ConduitResult<set_display_name::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -49,8 +48,9 @@ pub async fn set_displayname_route(
|
||||
)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"Tried to send displayname update for user not in the room.",
|
||||
)
|
||||
"Tried to send displayname update for user not in the \
|
||||
room.",
|
||||
)
|
||||
})?
|
||||
.content
|
||||
.clone(),
|
||||
@@ -69,9 +69,19 @@ pub async fn set_displayname_route(
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
let _ = db
|
||||
.rooms
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db);
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let _ =
|
||||
db.rooms
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db, &mutex_lock);
|
||||
|
||||
// Presence update
|
||||
db.rooms.edus.update_presence(
|
||||
@@ -107,7 +117,7 @@ pub async fn set_displayname_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_displayname_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_display_name::Request<'_>>,
|
||||
) -> ConduitResult<get_display_name::Response> {
|
||||
Ok(get_display_name::Response {
|
||||
@@ -122,7 +132,7 @@ pub async fn get_displayname_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_avatar_url_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_avatar_url::Request<'_>>,
|
||||
) -> ConduitResult<set_avatar_url::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -150,8 +160,9 @@ pub async fn set_avatar_url_route(
|
||||
)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"Tried to send displayname update for user not in the room.",
|
||||
)
|
||||
"Tried to send displayname update for user not in the \
|
||||
room.",
|
||||
)
|
||||
})?
|
||||
.content
|
||||
.clone(),
|
||||
@@ -170,9 +181,19 @@ pub async fn set_avatar_url_route(
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
let _ = db
|
||||
.rooms
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db);
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let _ =
|
||||
db.rooms
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db, &mutex_lock);
|
||||
|
||||
// Presence update
|
||||
db.rooms.edus.update_presence(
|
||||
@@ -208,7 +229,7 @@ pub async fn set_avatar_url_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_avatar_url_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_avatar_url::Request<'_>>,
|
||||
) -> ConduitResult<get_avatar_url::Response> {
|
||||
Ok(get_avatar_url::Response {
|
||||
@@ -223,7 +244,7 @@ pub async fn get_avatar_url_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_profile_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_profile::Request<'_>>,
|
||||
) -> ConduitResult<get_profile::Response> {
|
||||
if !db.users.exists(&body.user_id)? {
|
||||
|
||||
+11
-12
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -22,7 +21,7 @@ use rocket::{delete, get, post, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrules_all_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_pushrules_all::Request>,
|
||||
) -> ConduitResult<get_pushrules_all::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -47,7 +46,7 @@ pub async fn get_pushrules_all_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_pushrule::Request<'_>>,
|
||||
) -> ConduitResult<get_pushrule::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -101,7 +100,7 @@ pub async fn get_pushrule_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, req))]
|
||||
pub async fn set_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
req: Ruma<set_pushrule::Request<'_>>,
|
||||
) -> ConduitResult<set_pushrule::Response> {
|
||||
let sender_user = req.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -204,7 +203,7 @@ pub async fn set_pushrule_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_actions_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_pushrule_actions::Request<'_>>,
|
||||
) -> ConduitResult<get_pushrule_actions::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -263,7 +262,7 @@ pub async fn get_pushrule_actions_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushrule_actions_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_pushrule_actions::Request<'_>>,
|
||||
) -> ConduitResult<set_pushrule_actions::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -337,7 +336,7 @@ pub async fn set_pushrule_actions_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_enabled_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_pushrule_enabled::Request<'_>>,
|
||||
) -> ConduitResult<get_pushrule_enabled::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -398,7 +397,7 @@ pub async fn get_pushrule_enabled_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushrule_enabled_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_pushrule_enabled::Request<'_>>,
|
||||
) -> ConduitResult<set_pushrule_enabled::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -477,7 +476,7 @@ pub async fn set_pushrule_enabled_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_pushrule::Request<'_>>,
|
||||
) -> ConduitResult<delete_pushrule::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -546,7 +545,7 @@ pub async fn delete_pushrule_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushers_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_pushers::Request>,
|
||||
) -> ConduitResult<get_pushers::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -563,7 +562,7 @@ pub async fn get_pushers_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushers_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_pusher::Request>,
|
||||
) -> ConduitResult<set_pusher::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{read_marker::set_read_marker, receipt::create_receipt},
|
||||
},
|
||||
events::{AnyEphemeralRoomEvent, AnyEvent, EventType},
|
||||
events::{AnyEphemeralRoomEvent, EventType},
|
||||
receipt::ReceiptType,
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::post;
|
||||
use std::{collections::BTreeMap, time::SystemTime};
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
@@ -18,7 +19,7 @@ use std::{collections::BTreeMap, time::SystemTime};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_read_marker_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<set_read_marker::Request<'_>>,
|
||||
) -> ConduitResult<set_read_marker::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -27,7 +28,6 @@ pub async fn set_read_marker_route(
|
||||
content: ruma::events::fully_read::FullyReadEventContent {
|
||||
event_id: body.fully_read.clone(),
|
||||
},
|
||||
room_id: body.room_id.clone(),
|
||||
};
|
||||
db.account_data.update(
|
||||
Some(&body.room_id),
|
||||
@@ -54,26 +54,23 @@ pub async fn set_read_marker_route(
|
||||
user_receipts.insert(
|
||||
sender_user.clone(),
|
||||
ruma::events::receipt::Receipt {
|
||||
ts: Some(SystemTime::now()),
|
||||
ts: Some(MilliSecondsSinceUnixEpoch::now()),
|
||||
},
|
||||
);
|
||||
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(ReceiptType::Read, user_receipts);
|
||||
|
||||
let mut receipt_content = BTreeMap::new();
|
||||
receipt_content.insert(
|
||||
event.to_owned(),
|
||||
ruma::events::receipt::Receipts {
|
||||
read: Some(user_receipts),
|
||||
},
|
||||
);
|
||||
receipt_content.insert(event.to_owned(), receipts);
|
||||
|
||||
db.rooms.edus.readreceipt_update(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
|
||||
ruma::events::receipt::ReceiptEvent {
|
||||
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
|
||||
room_id: body.room_id.clone(),
|
||||
},
|
||||
)),
|
||||
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
|
||||
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
|
||||
room_id: body.room_id.clone(),
|
||||
}),
|
||||
&db.globals,
|
||||
)?;
|
||||
}
|
||||
@@ -89,7 +86,7 @@ pub async fn set_read_marker_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_receipt_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_receipt::Request<'_>>,
|
||||
) -> ConduitResult<create_receipt::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -112,26 +109,22 @@ pub async fn create_receipt_route(
|
||||
user_receipts.insert(
|
||||
sender_user.clone(),
|
||||
ruma::events::receipt::Receipt {
|
||||
ts: Some(SystemTime::now()),
|
||||
ts: Some(MilliSecondsSinceUnixEpoch::now()),
|
||||
},
|
||||
);
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(ReceiptType::Read, user_receipts);
|
||||
|
||||
let mut receipt_content = BTreeMap::new();
|
||||
receipt_content.insert(
|
||||
body.event_id.to_owned(),
|
||||
ruma::events::receipt::Receipts {
|
||||
read: Some(user_receipts),
|
||||
},
|
||||
);
|
||||
receipt_content.insert(body.event_id.to_owned(), receipts);
|
||||
|
||||
db.rooms.edus.readreceipt_update(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
|
||||
ruma::events::receipt::ReceiptEvent {
|
||||
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
|
||||
room_id: body.room_id.clone(),
|
||||
},
|
||||
)),
|
||||
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
|
||||
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
|
||||
room_id: body.room_id.clone(),
|
||||
}),
|
||||
&db.globals,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::State;
|
||||
use crate::{pdu::PduBuilder, ConduitResult, Database, Ruma};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{database::DatabaseGuard, pdu::PduBuilder, ConduitResult, Ruma};
|
||||
use ruma::{
|
||||
api::client::r0::redact::redact_event,
|
||||
events::{room::redaction, EventType},
|
||||
@@ -14,11 +15,21 @@ use rocket::put;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn redact_event_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<redact_event::Request<'_>>,
|
||||
) -> ConduitResult<redact_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let event_id = db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomRedaction,
|
||||
@@ -33,8 +44,11 @@ pub async fn redact_event_route(
|
||||
&sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(redact_event::Response { event_id }.into())
|
||||
|
||||
+66
-28
@@ -1,6 +1,7 @@
|
||||
use super::State;
|
||||
use crate::client_server::invite_helper;
|
||||
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{
|
||||
client_server::invite_helper, database::DatabaseGuard, pdu::PduBuilder, ConduitResult, Error,
|
||||
Ruma,
|
||||
};
|
||||
use log::info;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -14,7 +15,7 @@ use ruma::{
|
||||
serde::Raw,
|
||||
RoomAliasId, RoomId, RoomVersionId,
|
||||
};
|
||||
use std::{cmp::max, collections::BTreeMap, convert::TryFrom};
|
||||
use std::{cmp::max, collections::BTreeMap, convert::TryFrom, sync::Arc};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post};
|
||||
@@ -25,13 +26,23 @@ use rocket::{get, post};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_room_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_room::Request<'_>>,
|
||||
) -> ConduitResult<create_room::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let room_id = RoomId::new(db.globals.server_name());
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let alias = body
|
||||
.room_alias_name
|
||||
.as_ref()
|
||||
@@ -68,6 +79,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 2. Let the room creator join
|
||||
@@ -89,6 +101,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 3. Power levels
|
||||
@@ -112,27 +125,26 @@ pub async fn create_room_route(
|
||||
}
|
||||
}
|
||||
|
||||
let power_levels_content = if let Some(power_levels) = &body.power_level_content_override {
|
||||
serde_json::from_str(power_levels.json().get()).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
|
||||
})?
|
||||
} else {
|
||||
let mut power_levels_content =
|
||||
serde_json::to_value(ruma::events::room::power_levels::PowerLevelsEventContent {
|
||||
ban: 50.into(),
|
||||
events: BTreeMap::new(),
|
||||
events_default: 0.into(),
|
||||
invite: 50.into(),
|
||||
kick: 50.into(),
|
||||
redact: 50.into(),
|
||||
state_default: 50.into(),
|
||||
users,
|
||||
users_default: 0.into(),
|
||||
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
|
||||
room: 50.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.expect("event is valid, we just created it")
|
||||
};
|
||||
.expect("event is valid, we just created it");
|
||||
|
||||
if let Some(power_level_content_override) = &body.power_level_content_override {
|
||||
let json = serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(
|
||||
power_level_content_override.json().get(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
|
||||
})?;
|
||||
|
||||
for (key, value) in json {
|
||||
power_levels_content[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomPowerLevels,
|
||||
@@ -144,6 +156,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4. Events set by preset
|
||||
@@ -170,6 +183,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4.2 History Visibility
|
||||
@@ -187,6 +201,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 4.3 Guest Access
|
||||
@@ -212,6 +227,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// 5. Events listed in initial_state
|
||||
@@ -227,7 +243,7 @@ pub async fn create_room_route(
|
||||
}
|
||||
|
||||
db.rooms
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db)?;
|
||||
.build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db, &mutex_lock)?;
|
||||
}
|
||||
|
||||
// 6. Events implied by name and topic
|
||||
@@ -248,6 +264,7 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -266,10 +283,12 @@ pub async fn create_room_route(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
// 7. Events implied by invite (and TODO: invite_3pid)
|
||||
drop(mutex_lock);
|
||||
for user_id in &body.invite {
|
||||
let _ = invite_helper(sender_user, user_id, &room_id, &db, body.is_direct).await;
|
||||
}
|
||||
@@ -296,7 +315,7 @@ pub async fn create_room_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_room_event_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_room_event::Request<'_>>,
|
||||
) -> ConduitResult<get_room_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -324,7 +343,7 @@ pub async fn get_room_event_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upgrade_room_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<upgrade_room::Request<'_>>,
|
||||
_room_id: String,
|
||||
) -> ConduitResult<upgrade_room::Response> {
|
||||
@@ -340,6 +359,16 @@ pub async fn upgrade_room_route(
|
||||
// Create a replacement room
|
||||
let replacement_room = RoomId::new(db.globals.server_name());
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not intended to be used any further
|
||||
// Fail if the sender does not have the required permissions
|
||||
let tombstone_event_id = db.rooms.build_and_append_pdu(
|
||||
@@ -357,6 +386,7 @@ pub async fn upgrade_room_route(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// Get the old room federations status
|
||||
@@ -364,7 +394,8 @@ pub async fn upgrade_room_route(
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomCreate, "")?
|
||||
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
|
||||
.content,
|
||||
.content
|
||||
.clone(),
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
@@ -396,6 +427,7 @@ pub async fn upgrade_room_route(
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// Join the new room
|
||||
@@ -417,6 +449,7 @@ pub async fn upgrade_room_route(
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
// Recommended transferable state events list from the specs
|
||||
@@ -450,6 +483,7 @@ pub async fn upgrade_room_route(
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -465,7 +499,8 @@ pub async fn upgrade_room_route(
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")?
|
||||
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
|
||||
.content,
|
||||
.content
|
||||
.clone(),
|
||||
)
|
||||
.expect("database contains invalid PDU")
|
||||
.deserialize()
|
||||
@@ -492,8 +527,11 @@ pub async fn upgrade_room_route(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
drop(mutex_lock);
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
// Return the replacement room id
|
||||
|
||||
+42
-16
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::api::client::{error::ErrorKind, r0::search::search_events};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -13,23 +12,38 @@ use std::collections::BTreeMap;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn search_events_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<search_events::Request<'_>>,
|
||||
) -> ConduitResult<search_events::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let search_criteria = body.search_categories.room_events.as_ref().unwrap();
|
||||
let filter = search_criteria.filter.as_ref().unwrap();
|
||||
let filter = search_criteria.filter.clone().unwrap_or_default();
|
||||
|
||||
let room_id = filter.rooms.as_ref().unwrap().first().unwrap();
|
||||
let room_ids = filter.rooms.clone().unwrap_or_else(|| {
|
||||
db.rooms
|
||||
.rooms_joined(&sender_user)
|
||||
.filter_map(|r| r.ok())
|
||||
.collect()
|
||||
});
|
||||
|
||||
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
|
||||
|
||||
if !db.rooms.is_joined(sender_user, &room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
let mut searches = Vec::new();
|
||||
|
||||
for room_id in room_ids {
|
||||
if !db.rooms.is_joined(sender_user, &room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
let search = db
|
||||
.rooms
|
||||
.search_pdus(&room_id, &search_criteria.search_term)?;
|
||||
|
||||
searches.push(search.0.peekable());
|
||||
}
|
||||
|
||||
let skip = match body.next_batch.as_ref().map(|s| s.parse()) {
|
||||
@@ -43,12 +57,20 @@ pub async fn search_events_route(
|
||||
None => 0, // Default to the start
|
||||
};
|
||||
|
||||
let search = db
|
||||
.rooms
|
||||
.search_pdus(&room_id, &search_criteria.search_term)?;
|
||||
let mut results = Vec::new();
|
||||
for _ in 0..skip + limit {
|
||||
if let Some(s) = searches
|
||||
.iter_mut()
|
||||
.map(|s| (s.peek().cloned(), s))
|
||||
.max_by_key(|(peek, _)| peek.clone())
|
||||
.and_then(|(_, i)| i.next())
|
||||
{
|
||||
results.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
let results = search
|
||||
.0
|
||||
let results = results
|
||||
.iter()
|
||||
.map(|result| {
|
||||
Ok::<_, Error>(SearchResult {
|
||||
context: EventContextResult {
|
||||
@@ -83,7 +105,11 @@ pub async fn search_events_route(
|
||||
next_batch,
|
||||
results,
|
||||
state: BTreeMap::new(), // TODO
|
||||
highlights: search.1,
|
||||
highlights: search_criteria
|
||||
.search_term
|
||||
.split_terminator(|c: char| !c.is_alphanumeric())
|
||||
.map(str::to_lowercase)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
})
|
||||
.into())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{utils, ConduitResult, Database, Error, Ruma};
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{database::DatabaseGuard, utils, ConduitResult, Error, Ruma};
|
||||
use log::info;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -50,7 +50,7 @@ pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response>
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn login_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<login::Request<'_>>,
|
||||
) -> ConduitResult<login::Response> {
|
||||
// Validate login method
|
||||
@@ -167,7 +167,7 @@ pub async fn login_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn logout_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<logout::Request>,
|
||||
) -> ConduitResult<logout::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -195,15 +195,13 @@ pub async fn logout_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn logout_all_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<logout_all::Request>,
|
||||
) -> ConduitResult<logout_all::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
for device_id in db.users.all_device_ids(sender_user) {
|
||||
if let Ok(device_id) = device_id {
|
||||
db.users.remove_device(&sender_user, &device_id)?;
|
||||
}
|
||||
for device_id in db.users.all_device_ids(sender_user).flatten() {
|
||||
db.users.remove_device(&sender_user, &device_id)?;
|
||||
}
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
+26
-12
@@ -1,5 +1,8 @@
|
||||
use super::State;
|
||||
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
database::DatabaseGuard, pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -25,7 +28,7 @@ use rocket::{get, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_state_event_for_key_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<send_state_event::Request<'_>>,
|
||||
) -> ConduitResult<send_state_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -51,7 +54,7 @@ pub async fn send_state_event_for_key_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_state_event_for_empty_key_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<send_state_event::Request<'_>>,
|
||||
) -> ConduitResult<send_state_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -77,7 +80,7 @@ pub async fn send_state_event_for_empty_key_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_state_events::Request<'_>>,
|
||||
) -> ConduitResult<get_state_events::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -90,7 +93,7 @@ pub async fn get_state_events_route(
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
@@ -124,7 +127,7 @@ pub async fn get_state_events_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_for_key_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_state_events_for_key::Request<'_>>,
|
||||
) -> ConduitResult<get_state_events_for_key::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -137,7 +140,7 @@ pub async fn get_state_events_for_key_route(
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
@@ -163,7 +166,7 @@ pub async fn get_state_events_for_key_route(
|
||||
))?;
|
||||
|
||||
Ok(get_state_events_for_key::Response {
|
||||
content: serde_json::from_value(event.content)
|
||||
content: serde_json::from_value(event.content.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
|
||||
}
|
||||
.into())
|
||||
@@ -175,7 +178,7 @@ pub async fn get_state_events_for_key_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_for_empty_key_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_state_events_for_key::Request<'_>>,
|
||||
) -> ConduitResult<get_state_events_for_key::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -188,7 +191,7 @@ pub async fn get_state_events_for_empty_key_route(
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
|
||||
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
@@ -214,7 +217,7 @@ pub async fn get_state_events_for_empty_key_route(
|
||||
))?;
|
||||
|
||||
Ok(get_state_events_for_key::Response {
|
||||
content: serde_json::from_value(event.content)
|
||||
content: serde_json::from_value(event.content.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
|
||||
}
|
||||
.into())
|
||||
@@ -256,6 +259,16 @@ pub async fn send_state_event_for_key_helper(
|
||||
}
|
||||
}
|
||||
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
let event_id = db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type,
|
||||
@@ -267,6 +280,7 @@ pub async fn send_state_event_for_key_helper(
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&db,
|
||||
&mutex_lock,
|
||||
)?;
|
||||
|
||||
Ok(event_id)
|
||||
|
||||
+417
-229
@@ -1,19 +1,21 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Database, Error, Result, Ruma, RumaResponse};
|
||||
use log::error;
|
||||
use ruma::{
|
||||
api::client::r0::sync::sync_events,
|
||||
api::client::r0::{sync::sync_events, uiaa::UiaaResponse},
|
||||
events::{room::member::MembershipState, AnySyncEphemeralRoomEvent, EventType},
|
||||
serde::Raw,
|
||||
RoomId, UserId,
|
||||
DeviceId, RoomId, UserId,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::sync::watch::Sender;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, tokio};
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// # `GET /_matrix/client/r0/sync`
|
||||
///
|
||||
@@ -32,23 +34,142 @@ use std::{
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn sync_events_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<sync_events::Request<'_>>,
|
||||
) -> ConduitResult<sync_events::Response> {
|
||||
) -> std::result::Result<RumaResponse<sync_events::Response>, RumaResponse<UiaaResponse>> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
let arc_db = Arc::new(db);
|
||||
|
||||
let mut rx = match arc_db
|
||||
.globals
|
||||
.sync_receivers
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry((sender_user.clone(), sender_device.clone()))
|
||||
{
|
||||
Entry::Vacant(v) => {
|
||||
let (tx, rx) = tokio::sync::watch::channel(None);
|
||||
|
||||
tokio::spawn(sync_helper_wrapper(
|
||||
Arc::clone(&arc_db),
|
||||
sender_user.clone(),
|
||||
sender_device.clone(),
|
||||
body.since.clone(),
|
||||
body.full_state,
|
||||
body.timeout,
|
||||
tx,
|
||||
));
|
||||
|
||||
v.insert((body.since.clone(), rx)).1.clone()
|
||||
}
|
||||
Entry::Occupied(mut o) => {
|
||||
if o.get().0 != body.since {
|
||||
let (tx, rx) = tokio::sync::watch::channel(None);
|
||||
|
||||
tokio::spawn(sync_helper_wrapper(
|
||||
Arc::clone(&arc_db),
|
||||
sender_user.clone(),
|
||||
sender_device.clone(),
|
||||
body.since.clone(),
|
||||
body.full_state,
|
||||
body.timeout,
|
||||
tx,
|
||||
));
|
||||
|
||||
o.insert((body.since.clone(), rx.clone()));
|
||||
|
||||
rx
|
||||
} else {
|
||||
o.get().1.clone()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let we_have_to_wait = rx.borrow().is_none();
|
||||
if we_have_to_wait {
|
||||
if let Err(e) = rx.changed().await {
|
||||
error!("Error waiting for sync: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let result = match rx
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.expect("When sync channel changes it's always set to some")
|
||||
{
|
||||
Ok(response) => Ok(response.clone()),
|
||||
Err(error) => Err(error.to_response()),
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn sync_helper_wrapper(
|
||||
db: Arc<DatabaseGuard>,
|
||||
sender_user: UserId,
|
||||
sender_device: Box<DeviceId>,
|
||||
since: Option<String>,
|
||||
full_state: bool,
|
||||
timeout: Option<Duration>,
|
||||
tx: Sender<Option<ConduitResult<sync_events::Response>>>,
|
||||
) {
|
||||
let r = sync_helper(
|
||||
Arc::clone(&db),
|
||||
sender_user.clone(),
|
||||
sender_device.clone(),
|
||||
since.clone(),
|
||||
full_state,
|
||||
timeout,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok((_, caching_allowed)) = r {
|
||||
if !caching_allowed {
|
||||
match db
|
||||
.globals
|
||||
.sync_receivers
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry((sender_user, sender_device))
|
||||
{
|
||||
Entry::Occupied(o) => {
|
||||
// Only remove if the device didn't start a different /sync already
|
||||
if o.get().0 == since {
|
||||
o.remove();
|
||||
}
|
||||
}
|
||||
Entry::Vacant(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(db);
|
||||
|
||||
let _ = tx.send(Some(r.map(|(r, _)| r.into())));
|
||||
}
|
||||
|
||||
async fn sync_helper(
|
||||
db: Arc<DatabaseGuard>,
|
||||
sender_user: UserId,
|
||||
sender_device: Box<DeviceId>,
|
||||
since: Option<String>,
|
||||
full_state: bool,
|
||||
timeout: Option<Duration>,
|
||||
// bool = caching allowed
|
||||
) -> std::result::Result<(sync_events::Response, bool), Error> {
|
||||
// TODO: match body.set_presence {
|
||||
db.rooms.edus.ping_presence(&sender_user)?;
|
||||
|
||||
// Setup watchers, so if there's no response, we can wait for them
|
||||
let watcher = db.watch(sender_user, sender_device);
|
||||
let watcher = db.watch(&sender_user, &sender_device);
|
||||
|
||||
let next_batch = db.globals.current_count()?.to_string();
|
||||
let next_batch = db.globals.current_count()?;
|
||||
let next_batch_string = next_batch.to_string();
|
||||
|
||||
let mut joined_rooms = BTreeMap::new();
|
||||
let since = body
|
||||
.since
|
||||
let since = since
|
||||
.clone()
|
||||
.and_then(|string| string.parse().ok())
|
||||
.unwrap_or(0);
|
||||
@@ -68,15 +189,37 @@ pub async fn sync_events_route(
|
||||
for room_id in db.rooms.rooms_joined(&sender_user) {
|
||||
let room_id = room_id?;
|
||||
|
||||
// Get and drop the lock to wait for remaining operations to finish
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
drop(mutex_lock);
|
||||
|
||||
let mut non_timeline_pdus = db
|
||||
.rooms
|
||||
.pdus_since(&sender_user, &room_id, since)?
|
||||
.filter_map(|r| r.ok()); // Filter out buggy events
|
||||
.pdus_until(&sender_user, &room_id, u64::MAX)
|
||||
.filter_map(|r| {
|
||||
// Filter out buggy events
|
||||
if r.is_err() {
|
||||
error!("Bad pdu in pdus_since: {:?}", r);
|
||||
}
|
||||
r.ok()
|
||||
})
|
||||
.take_while(|(pduid, _)| {
|
||||
db.rooms
|
||||
.pdu_count(pduid)
|
||||
.map_or(false, |count| count > since)
|
||||
});
|
||||
|
||||
// Take the last 10 events for the timeline
|
||||
let timeline_pdus = non_timeline_pdus
|
||||
.by_ref()
|
||||
.rev()
|
||||
.take(10)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
@@ -96,24 +239,95 @@ pub async fn sync_events_route(
|
||||
|
||||
// Database queries:
|
||||
|
||||
let current_shortstatehash = db.rooms.current_shortstatehash(&room_id)?;
|
||||
let current_shortstatehash = db
|
||||
.rooms
|
||||
.current_shortstatehash(&room_id)?
|
||||
.expect("All rooms have state");
|
||||
|
||||
let first_pdu_before_since = db
|
||||
.rooms
|
||||
.pdus_until(&sender_user, &room_id, since)
|
||||
.next()
|
||||
.transpose()?;
|
||||
|
||||
// These type is Option<Option<_>>. The outer Option is None when there is no event between
|
||||
// since and the current room state, meaning there should be no updates.
|
||||
// The inner Option is None when there is an event, but there is no state hash associated
|
||||
// with it. This can happen for the RoomCreate event, so all updates should arrive.
|
||||
let first_pdu_before_since = db.rooms.pdus_until(sender_user, &room_id, since).next();
|
||||
let pdus_after_since = db
|
||||
.rooms
|
||||
.pdus_after(sender_user, &room_id, since)
|
||||
.pdus_after(&sender_user, &room_id, since)
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
let since_shortstatehash = first_pdu_before_since.as_ref().map(|pdu| {
|
||||
db.rooms
|
||||
.pdu_shortstatehash(&pdu.as_ref().ok()?.1.event_id)
|
||||
.ok()?
|
||||
});
|
||||
let since_shortstatehash = first_pdu_before_since
|
||||
.as_ref()
|
||||
.map(|pdu| {
|
||||
db.rooms
|
||||
.pdu_shortstatehash(&pdu.1.event_id)
|
||||
.transpose()
|
||||
.expect("all pdus have state")
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// Calculates joined_member_count, invited_member_count and heroes
|
||||
let calculate_counts = || {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
|
||||
|
||||
// Recalculate heroes (first 5 members)
|
||||
let mut heroes = Vec::new();
|
||||
|
||||
if joined_member_count + invited_member_count <= 5 {
|
||||
// Go through all PDUs and for each member event, check if the user is still joined or
|
||||
// invited until we have 5 or we reach the end
|
||||
|
||||
for hero in db
|
||||
.rooms
|
||||
.all_pdus(&sender_user, &room_id)
|
||||
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
|
||||
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
|
||||
.map(|(_, pdu)| {
|
||||
let content = serde_json::from_value::<
|
||||
ruma::events::room::member::MemberEventContent,
|
||||
>(pdu.content.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let user_id = UserId::try_from(state_key.clone()).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId in member PDU.")
|
||||
})?;
|
||||
|
||||
// The membership was and still is invite or join
|
||||
if matches!(
|
||||
content.membership,
|
||||
MembershipState::Join | MembershipState::Invite
|
||||
) && (db.rooms.is_joined(&user_id, &room_id)?
|
||||
|| db.rooms.is_invited(&user_id, &room_id)?)
|
||||
{
|
||||
Ok::<_, Error>(Some(state_key.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
// Filter out buggy users
|
||||
.filter_map(|u| u.ok())
|
||||
// Filter for possible heroes
|
||||
.flatten()
|
||||
{
|
||||
if heroes.contains(&hero) || hero == sender_user.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
heroes.push(hero);
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Some(joined_member_count),
|
||||
Some(invited_member_count),
|
||||
heroes,
|
||||
)
|
||||
};
|
||||
|
||||
let (
|
||||
heroes,
|
||||
@@ -121,65 +335,107 @@ pub async fn sync_events_route(
|
||||
invited_member_count,
|
||||
joined_since_last_sync,
|
||||
state_events,
|
||||
) = if pdus_after_since && Some(current_shortstatehash) != since_shortstatehash {
|
||||
let current_state = db.rooms.room_state_full(&room_id)?;
|
||||
let current_members = current_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.map(|(key, value)| (&key.1, value)) // Only keep state key
|
||||
.collect::<Vec<_>>();
|
||||
let encrypted_room = current_state
|
||||
.get(&(EventType::RoomEncryption, "".to_owned()))
|
||||
.is_some();
|
||||
let since_state = since_shortstatehash
|
||||
.as_ref()
|
||||
.map(|since_shortstatehash| {
|
||||
Ok::<_, Error>(
|
||||
since_shortstatehash
|
||||
.map(|since_shortstatehash| db.rooms.state_full(since_shortstatehash))
|
||||
.transpose()?,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
) = if since_shortstatehash.is_none() {
|
||||
// Probably since = 0, we will do an initial sync
|
||||
let (joined_member_count, invited_member_count, heroes) = calculate_counts();
|
||||
|
||||
let since_encryption = since_state.as_ref().map(|state| {
|
||||
state
|
||||
.as_ref()
|
||||
.map(|state| state.get(&(EventType::RoomEncryption, "".to_owned())))
|
||||
});
|
||||
let current_state_ids = db.rooms.state_full_ids(current_shortstatehash)?;
|
||||
let state_events = current_state_ids
|
||||
.iter()
|
||||
.map(|id| db.rooms.get_pdu(id))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
heroes,
|
||||
joined_member_count,
|
||||
invited_member_count,
|
||||
true,
|
||||
state_events,
|
||||
)
|
||||
} else if !pdus_after_since || since_shortstatehash == Some(current_shortstatehash) {
|
||||
// No state changes
|
||||
(Vec::new(), None, None, false, Vec::new())
|
||||
} else {
|
||||
// Incremental /sync
|
||||
let since_shortstatehash = since_shortstatehash.unwrap();
|
||||
|
||||
let since_sender_member = db
|
||||
.rooms
|
||||
.state_get(
|
||||
since_shortstatehash,
|
||||
&EventType::RoomMember,
|
||||
sender_user.as_str(),
|
||||
)?
|
||||
.and_then(|pdu| {
|
||||
serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
|
||||
pdu.content.clone(),
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))
|
||||
.ok()
|
||||
});
|
||||
|
||||
let joined_since_last_sync = since_sender_member
|
||||
.map_or(true, |member| member.membership != MembershipState::Join);
|
||||
|
||||
let current_state_ids = db.rooms.state_full_ids(current_shortstatehash)?;
|
||||
|
||||
let since_state_ids = db.rooms.state_full_ids(since_shortstatehash)?;
|
||||
|
||||
let state_events = if joined_since_last_sync {
|
||||
current_state_ids
|
||||
.iter()
|
||||
.map(|id| db.rooms.get_pdu(id))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
current_state_ids
|
||||
.difference(&since_state_ids)
|
||||
.filter(|id| {
|
||||
!timeline_pdus
|
||||
.iter()
|
||||
.any(|(_, timeline_pdu)| timeline_pdu.event_id == **id)
|
||||
})
|
||||
.map(|id| db.rooms.get_pdu(id))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let encrypted_room = db
|
||||
.rooms
|
||||
.state_get(current_shortstatehash, &EventType::RoomEncryption, "")?
|
||||
.is_some();
|
||||
|
||||
let since_encryption =
|
||||
db.rooms
|
||||
.state_get(since_shortstatehash, &EventType::RoomEncryption, "")?;
|
||||
|
||||
// Calculations:
|
||||
let new_encrypted_room =
|
||||
encrypted_room && since_encryption.map_or(true, |encryption| encryption.is_none());
|
||||
let new_encrypted_room = encrypted_room && since_encryption.is_none();
|
||||
|
||||
let send_member_count = since_state.as_ref().map_or(true, |since_state| {
|
||||
since_state.as_ref().map_or(true, |since_state| {
|
||||
current_members.len()
|
||||
!= since_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.count()
|
||||
})
|
||||
});
|
||||
|
||||
let since_sender_member = since_state.as_ref().map(|since_state| {
|
||||
since_state.as_ref().and_then(|state| {
|
||||
state
|
||||
.get(&(EventType::RoomMember, sender_user.as_str().to_owned()))
|
||||
.and_then(|pdu| {
|
||||
serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
});
|
||||
let send_member_count = state_events
|
||||
.iter()
|
||||
.any(|event| event.kind == EventType::RoomMember);
|
||||
|
||||
if encrypted_room {
|
||||
for (user_id, current_member) in current_members {
|
||||
for (user_id, current_member) in db
|
||||
.rooms
|
||||
.room_members(&room_id)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|user_id| {
|
||||
db.rooms
|
||||
.state_get(
|
||||
current_shortstatehash,
|
||||
&EventType::RoomMember,
|
||||
user_id.as_str(),
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|current_member| (user_id, current_member))
|
||||
})
|
||||
{
|
||||
let current_membership = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(current_member.content.clone())
|
||||
@@ -188,31 +444,23 @@ pub async fn sync_events_route(
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?
|
||||
.membership;
|
||||
|
||||
let since_membership =
|
||||
since_state
|
||||
.as_ref()
|
||||
.map_or(MembershipState::Leave, |since_state| {
|
||||
since_state
|
||||
.as_ref()
|
||||
.and_then(|since_state| {
|
||||
since_state
|
||||
.get(&(EventType::RoomMember, user_id.clone()))
|
||||
.and_then(|since_member| {
|
||||
serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(
|
||||
since_member.content.clone()
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid PDU in database.")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
.map_or(MembershipState::Leave, |member| member.membership)
|
||||
});
|
||||
let since_membership = db
|
||||
.rooms
|
||||
.state_get(
|
||||
since_shortstatehash,
|
||||
&EventType::RoomMember,
|
||||
user_id.as_str(),
|
||||
)?
|
||||
.and_then(|since_member| {
|
||||
serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(since_member.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))
|
||||
.ok()
|
||||
})
|
||||
.map_or(MembershipState::Leave, |member| member.membership);
|
||||
|
||||
let user_id = UserId::try_from(user_id.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
|
||||
@@ -220,10 +468,11 @@ pub async fn sync_events_route(
|
||||
match (since_membership, current_membership) {
|
||||
(MembershipState::Leave, MembershipState::Join) => {
|
||||
// A new user joined an encrypted room
|
||||
if !share_encrypted_room(&db, &sender_user, &user_id, &room_id) {
|
||||
if !share_encrypted_room(&db, &sender_user, &user_id, &room_id)? {
|
||||
device_list_updates.insert(user_id);
|
||||
}
|
||||
}
|
||||
// TODO: Remove, this should never happen here, right?
|
||||
(MembershipState::Join, MembershipState::Leave) => {
|
||||
// Write down users that have left encrypted rooms we are in
|
||||
left_encrypted_users.insert(user_id);
|
||||
@@ -233,123 +482,30 @@ pub async fn sync_events_route(
|
||||
}
|
||||
}
|
||||
|
||||
let joined_since_last_sync = since_sender_member.map_or(true, |member| {
|
||||
member.map_or(true, |member| member.membership != MembershipState::Join)
|
||||
});
|
||||
|
||||
if joined_since_last_sync && encrypted_room || new_encrypted_room {
|
||||
// If the user is in a new encrypted room, give them all joined users
|
||||
device_list_updates.extend(
|
||||
db.rooms
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user_id| Some(user_id.ok()?))
|
||||
.flatten()
|
||||
.filter(|user_id| {
|
||||
// Don't send key updates from the sender to the sender
|
||||
sender_user != user_id
|
||||
&sender_user != user_id
|
||||
})
|
||||
.filter(|user_id| {
|
||||
// Only send keys if the sender doesn't share an encrypted room with the target already
|
||||
!share_encrypted_room(&db, sender_user, user_id, &room_id)
|
||||
!share_encrypted_room(&db, &sender_user, user_id, &room_id)
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
|
||||
|
||||
// Recalculate heroes (first 5 members)
|
||||
let mut heroes = Vec::new();
|
||||
|
||||
if joined_member_count + invited_member_count <= 5 {
|
||||
// Go through all PDUs and for each member event, check if the user is still joined or
|
||||
// invited until we have 5 or we reach the end
|
||||
|
||||
for hero in db
|
||||
.rooms
|
||||
.all_pdus(&sender_user, &room_id)?
|
||||
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
|
||||
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
|
||||
.map(|(_, pdu)| {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid member event in database.")
|
||||
})?;
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let user_id =
|
||||
UserId::try_from(state_key.clone()).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId in member PDU.")
|
||||
})?;
|
||||
|
||||
// The membership was and still is invite or join
|
||||
if matches!(
|
||||
content.membership,
|
||||
MembershipState::Join | MembershipState::Invite
|
||||
) && (db.rooms.is_joined(&user_id, &room_id)?
|
||||
|| db.rooms.is_invited(&user_id, &room_id)?)
|
||||
{
|
||||
Ok::<_, Error>(Some(state_key.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.filter_map(|u| u.ok()) // Filter out buggy users
|
||||
// Filter for possible heroes
|
||||
.filter_map(|u| u)
|
||||
{
|
||||
if heroes.contains(&hero) || hero == sender_user.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
heroes.push(hero);
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Some(joined_member_count),
|
||||
Some(invited_member_count),
|
||||
heroes,
|
||||
)
|
||||
calculate_counts()
|
||||
} else {
|
||||
(None, None, Vec::new())
|
||||
};
|
||||
|
||||
let state_events = if joined_since_last_sync {
|
||||
current_state
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect()
|
||||
} else {
|
||||
match since_state {
|
||||
None => Vec::new(),
|
||||
Some(Some(since_state)) => current_state
|
||||
.iter()
|
||||
.filter(|(key, value)| {
|
||||
since_state.get(key).map(|e| &e.event_id) != Some(&value.event_id)
|
||||
})
|
||||
.filter(|(_, value)| {
|
||||
!timeline_pdus.iter().any(|(_, timeline_pdu)| {
|
||||
timeline_pdu.kind == value.kind
|
||||
&& timeline_pdu.state_key == value.state_key
|
||||
})
|
||||
})
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
Some(None) => current_state
|
||||
.iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
heroes,
|
||||
joined_member_count,
|
||||
@@ -357,8 +513,6 @@ pub async fn sync_events_route(
|
||||
joined_since_last_sync,
|
||||
state_events,
|
||||
)
|
||||
} else {
|
||||
(Vec::new(), None, None, false, Vec::new())
|
||||
};
|
||||
|
||||
// Look for device list updates in this room
|
||||
@@ -404,8 +558,9 @@ pub async fn sync_events_route(
|
||||
let mut edus = db
|
||||
.rooms
|
||||
.edus
|
||||
.readreceipts_since(&room_id, since)?
|
||||
.readreceipts_since(&room_id, since)
|
||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||
.map(|(_, _, v)| v)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if db.rooms.edus.last_typing_update(&room_id, &db.globals)? > since {
|
||||
@@ -421,7 +576,7 @@ pub async fn sync_events_route(
|
||||
}
|
||||
|
||||
let joined_room = sync_events::JoinedRoom {
|
||||
account_data: sync_events::AccountData {
|
||||
account_data: sync_events::RoomAccountData {
|
||||
events: db
|
||||
.account_data
|
||||
.changes_since(Some(&room_id), &sender_user, since)?
|
||||
@@ -448,7 +603,10 @@ pub async fn sync_events_route(
|
||||
events: room_events,
|
||||
},
|
||||
state: sync_events::State {
|
||||
events: state_events,
|
||||
events: state_events
|
||||
.iter()
|
||||
.map(|pdu| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
},
|
||||
ephemeral: sync_events::Ephemeral { events: edus },
|
||||
};
|
||||
@@ -495,6 +653,19 @@ pub async fn sync_events_route(
|
||||
let mut left_rooms = BTreeMap::new();
|
||||
for result in db.rooms.rooms_left(&sender_user) {
|
||||
let (room_id, left_state_events) = result?;
|
||||
|
||||
// Get and drop the lock to wait for remaining operations to finish
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
drop(mutex_lock);
|
||||
|
||||
let left_count = db.rooms.get_left_count(&room_id, &sender_user)?;
|
||||
|
||||
// Left before last sync
|
||||
@@ -505,10 +676,10 @@ pub async fn sync_events_route(
|
||||
left_rooms.insert(
|
||||
room_id.clone(),
|
||||
sync_events::LeftRoom {
|
||||
account_data: sync_events::AccountData { events: Vec::new() },
|
||||
account_data: sync_events::RoomAccountData { events: Vec::new() },
|
||||
timeline: sync_events::Timeline {
|
||||
limited: false,
|
||||
prev_batch: Some(next_batch.clone()),
|
||||
prev_batch: Some(next_batch_string.clone()),
|
||||
events: Vec::new(),
|
||||
},
|
||||
state: sync_events::State {
|
||||
@@ -521,6 +692,19 @@ pub async fn sync_events_route(
|
||||
let mut invited_rooms = BTreeMap::new();
|
||||
for result in db.rooms.rooms_invited(&sender_user) {
|
||||
let (room_id, invite_state_events) = result?;
|
||||
|
||||
// Get and drop the lock to wait for remaining operations to finish
|
||||
let mutex = Arc::clone(
|
||||
db.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
drop(mutex_lock);
|
||||
|
||||
let invite_count = db.rooms.get_invite_count(&room_id, &sender_user)?;
|
||||
|
||||
// Invited before last sync
|
||||
@@ -541,7 +725,7 @@ pub async fn sync_events_route(
|
||||
for user_id in left_encrypted_users {
|
||||
let still_share_encrypted_room = db
|
||||
.rooms
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|other_room_id| {
|
||||
Some(
|
||||
@@ -561,10 +745,10 @@ pub async fn sync_events_route(
|
||||
|
||||
// Remove all to-device events the device received *last time*
|
||||
db.users
|
||||
.remove_to_device_events(sender_user, sender_device, since)?;
|
||||
.remove_to_device_events(&sender_user, &sender_device, since)?;
|
||||
|
||||
let response = sync_events::Response {
|
||||
next_batch,
|
||||
next_batch: next_batch_string,
|
||||
rooms: sync_events::Rooms {
|
||||
leave: left_rooms,
|
||||
join: joined_rooms,
|
||||
@@ -576,7 +760,7 @@ pub async fn sync_events_route(
|
||||
.map(|(_, v)| Raw::from(v))
|
||||
.collect(),
|
||||
},
|
||||
account_data: sync_events::AccountData {
|
||||
account_data: sync_events::GlobalAccountData {
|
||||
events: db
|
||||
.account_data
|
||||
.changes_since(None, &sender_user, since)?
|
||||
@@ -592,20 +776,22 @@ pub async fn sync_events_route(
|
||||
changed: device_list_updates.into_iter().collect(),
|
||||
left: device_list_left.into_iter().collect(),
|
||||
},
|
||||
device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_user)? > since
|
||||
device_one_time_keys_count: if db.users.last_one_time_keys_update(&sender_user)? > since
|
||||
|| since == 0
|
||||
{
|
||||
db.users.count_one_time_keys(sender_user, sender_device)?
|
||||
db.users.count_one_time_keys(&sender_user, &sender_device)?
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
},
|
||||
to_device: sync_events::ToDevice {
|
||||
events: db.users.get_to_device_events(sender_user, sender_device)?,
|
||||
events: db
|
||||
.users
|
||||
.get_to_device_events(&sender_user, &sender_device)?,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Retry the endpoint instead of returning (waiting for #118)
|
||||
if !body.full_state
|
||||
if !full_state
|
||||
&& response.rooms.is_empty()
|
||||
&& response.presence.is_empty()
|
||||
&& response.account_data.is_empty()
|
||||
@@ -615,14 +801,15 @@ pub async fn sync_events_route(
|
||||
{
|
||||
// Hang a few seconds so requests are not spammed
|
||||
// Stop hanging if new info arrives
|
||||
let mut duration = body.timeout.unwrap_or_default();
|
||||
let mut duration = timeout.unwrap_or_default();
|
||||
if duration.as_secs() > 30 {
|
||||
duration = Duration::from_secs(30);
|
||||
}
|
||||
let _ = tokio::time::timeout(duration, watcher).await;
|
||||
Ok((response, false))
|
||||
} else {
|
||||
Ok((response, since != next_batch)) // Only cache if we made progress
|
||||
}
|
||||
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(db))]
|
||||
@@ -631,9 +818,10 @@ fn share_encrypted_room(
|
||||
sender_user: &UserId,
|
||||
user_id: &UserId,
|
||||
ignore_room: &RoomId,
|
||||
) -> bool {
|
||||
db.rooms
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])
|
||||
) -> Result<bool> {
|
||||
Ok(db
|
||||
.rooms
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|room_id| room_id != ignore_room)
|
||||
.filter_map(|other_room_id| {
|
||||
@@ -644,5 +832,5 @@ fn share_encrypted_room(
|
||||
.is_some(),
|
||||
)
|
||||
})
|
||||
.any(|encrypted| encrypted)
|
||||
.any(|encrypted| encrypted))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Ruma};
|
||||
use ruma::{
|
||||
api::client::r0::tag::{create_tag, delete_tag, get_tags},
|
||||
events::EventType,
|
||||
@@ -15,7 +14,7 @@ use rocket::{delete, get, put};
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_tag_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_tag::Request<'_>>,
|
||||
) -> ConduitResult<create_tag::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -31,7 +30,7 @@ pub async fn update_tag_route(
|
||||
tags_event
|
||||
.content
|
||||
.tags
|
||||
.insert(body.tag.to_string(), body.tag_info.clone());
|
||||
.insert(body.tag.clone().into(), body.tag_info.clone());
|
||||
|
||||
db.account_data.update(
|
||||
Some(&body.room_id),
|
||||
@@ -52,7 +51,7 @@ pub async fn update_tag_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_tag_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<delete_tag::Request<'_>>,
|
||||
) -> ConduitResult<delete_tag::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -65,7 +64,7 @@ pub async fn delete_tag_route(
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
});
|
||||
tags_event.content.tags.remove(&body.tag);
|
||||
tags_event.content.tags.remove(&body.tag.clone().into());
|
||||
|
||||
db.account_data.update(
|
||||
Some(&body.room_id),
|
||||
@@ -86,7 +85,7 @@ pub async fn delete_tag_route(
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_tags_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<get_tags::Request<'_>>,
|
||||
) -> ConduitResult<get_tags::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
r0::to_device::{self, send_event_to_device},
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{error::ErrorKind, r0::to_device::send_event_to_device},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -14,13 +13,15 @@ use rocket::put;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_event_to_device_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<send_event_to_device::Request<'_>>,
|
||||
) -> ConduitResult<send_event_to_device::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_deref();
|
||||
|
||||
// TODO: uncomment when https://github.com/vector-im/element-android/issues/3589 is solved
|
||||
// Check if this is a new transaction id
|
||||
/*
|
||||
if db
|
||||
.transaction_ids
|
||||
.existing_txnid(sender_user, sender_device, &body.txn_id)?
|
||||
@@ -28,31 +29,30 @@ pub async fn send_event_to_device_route(
|
||||
{
|
||||
return Ok(send_event_to_device::Response.into());
|
||||
}
|
||||
*/
|
||||
|
||||
for (target_user_id, map) in &body.messages {
|
||||
for (target_device_id_maybe, event) in map {
|
||||
match target_device_id_maybe {
|
||||
to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => {
|
||||
db.users.add_to_device_event(
|
||||
sender_user,
|
||||
&target_user_id,
|
||||
&target_device_id,
|
||||
&body.event_type,
|
||||
serde_json::from_str(event.get()).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
&db.globals,
|
||||
)?
|
||||
}
|
||||
DeviceIdOrAllDevices::DeviceId(target_device_id) => db.users.add_to_device_event(
|
||||
sender_user,
|
||||
&target_user_id,
|
||||
&target_device_id,
|
||||
&body.event_type,
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
&db.globals,
|
||||
)?,
|
||||
|
||||
to_device::DeviceIdOrAllDevices::AllDevices => {
|
||||
DeviceIdOrAllDevices::AllDevices => {
|
||||
for target_device_id in db.users.all_device_ids(&target_user_id) {
|
||||
db.users.add_to_device_event(
|
||||
sender_user,
|
||||
&target_user_id,
|
||||
&target_device_id?,
|
||||
&body.event_type,
|
||||
serde_json::from_str(event.get()).map_err(|_| {
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
&db.globals,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{utils, ConduitResult, Database, Ruma};
|
||||
use crate::{database::DatabaseGuard, utils, ConduitResult, Ruma};
|
||||
use create_typing_event::Typing;
|
||||
use ruma::api::client::r0::typing::create_typing_event;
|
||||
|
||||
@@ -12,7 +11,7 @@ use rocket::put;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub fn create_typing_event_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<create_typing_event::Request<'_>>,
|
||||
) -> ConduitResult<create_typing_event::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Ruma};
|
||||
use crate::{database::DatabaseGuard, ConduitResult, Ruma};
|
||||
use ruma::api::client::r0::user_directory::search_users;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -11,7 +10,7 @@ use rocket::post;
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn search_users_route(
|
||||
db: State<'_, Database>,
|
||||
db: DatabaseGuard,
|
||||
body: Ruma<search_users::Request<'_>>,
|
||||
) -> ConduitResult<search_users::Response> {
|
||||
let limit = u64::from(body.limit) as usize;
|
||||
@@ -19,9 +18,6 @@ pub async fn search_users_route(
|
||||
let mut users = db.users.iter().filter_map(|user_id| {
|
||||
// Filter out buggy users (they should not exist, but you never know...)
|
||||
let user_id = user_id.ok()?;
|
||||
if db.users.is_deactivated(&user_id).ok()? {
|
||||
return None;
|
||||
}
|
||||
|
||||
let user = search_users::User {
|
||||
user_id: user_id.clone(),
|
||||
@@ -29,11 +25,18 @@ pub async fn search_users_route(
|
||||
avatar_url: db.users.avatar_url(&user_id).ok()?,
|
||||
};
|
||||
|
||||
if !user.user_id.to_string().contains(&body.search_term)
|
||||
if !user
|
||||
.user_id
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.contains(&body.search_term.to_lowercase())
|
||||
&& user
|
||||
.display_name
|
||||
.as_ref()
|
||||
.filter(|name| name.contains(&body.search_term))
|
||||
.filter(|name| {
|
||||
name.to_lowercase()
|
||||
.contains(&body.search_term.to_lowercase())
|
||||
})
|
||||
.is_none()
|
||||
{
|
||||
return None;
|
||||
|
||||
+365
-91
@@ -1,9 +1,12 @@
|
||||
pub mod abstraction;
|
||||
|
||||
pub mod account_data;
|
||||
pub mod admin;
|
||||
pub mod appservice;
|
||||
pub mod globals;
|
||||
pub mod key_backups;
|
||||
pub mod media;
|
||||
pub mod proxy;
|
||||
pub mod pusher;
|
||||
pub mod rooms;
|
||||
pub mod sending;
|
||||
@@ -11,26 +14,44 @@ pub mod transaction_ids;
|
||||
pub mod uiaa;
|
||||
pub mod users;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::{utils, Error, Result};
|
||||
use abstraction::DatabaseEngine;
|
||||
use directories::ProjectDirs;
|
||||
use futures::StreamExt;
|
||||
use log::info;
|
||||
use rocket::futures::{self, channel::mpsc};
|
||||
use log::error;
|
||||
use lru_cache::LruCache;
|
||||
use rocket::{
|
||||
futures::{channel::mpsc, stream::FuturesUnordered, StreamExt},
|
||||
outcome::IntoOutcome,
|
||||
request::{FromRequest, Request},
|
||||
try_outcome, State,
|
||||
};
|
||||
use ruma::{DeviceId, ServerName, UserId};
|
||||
use serde::Deserialize;
|
||||
use serde::{de::IgnoredAny, Deserialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::remove_dir_all,
|
||||
collections::{BTreeMap, HashMap},
|
||||
fs::{self, remove_dir_all},
|
||||
io::Write,
|
||||
ops::Deref,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::sync::{OwnedRwLockReadGuard, RwLock as TokioRwLock, Semaphore};
|
||||
|
||||
use self::proxy::ProxyConfig;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
server_name: Box<ServerName>,
|
||||
database_path: String,
|
||||
#[serde(default = "default_cache_capacity")]
|
||||
cache_capacity: u32,
|
||||
#[serde(default = "default_db_cache_capacity")]
|
||||
db_cache_capacity: u32,
|
||||
#[serde(default = "default_sqlite_read_pool_size")]
|
||||
sqlite_read_pool_size: usize,
|
||||
#[serde(default = "false_fn")]
|
||||
sqlite_wal_clean_timer: bool,
|
||||
#[serde(default = "default_sqlite_wal_clean_second_interval")]
|
||||
sqlite_wal_clean_second_interval: u32,
|
||||
#[serde(default = "default_sqlite_wal_clean_second_timeout")]
|
||||
sqlite_wal_clean_second_timeout: u32,
|
||||
#[serde(default = "default_max_request_size")]
|
||||
max_request_size: u32,
|
||||
#[serde(default = "default_max_concurrent_requests")]
|
||||
@@ -43,11 +64,36 @@ pub struct Config {
|
||||
allow_federation: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
pub allow_jaeger: bool,
|
||||
#[serde(default)]
|
||||
proxy: ProxyConfig,
|
||||
jwt_secret: Option<String>,
|
||||
#[serde(default = "Vec::new")]
|
||||
trusted_servers: Vec<Box<ServerName>>,
|
||||
#[serde(default = "default_log")]
|
||||
pub log: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
catchall: BTreeMap<String, IgnoredAny>,
|
||||
}
|
||||
|
||||
const DEPRECATED_KEYS: &[&str] = &["cache_capacity"];
|
||||
|
||||
impl Config {
|
||||
pub fn warn_deprecated(&self) {
|
||||
let mut was_deprecated = false;
|
||||
for key in self
|
||||
.catchall
|
||||
.keys()
|
||||
.filter(|key| DEPRECATED_KEYS.iter().any(|s| s == key))
|
||||
{
|
||||
log::warn!("Config parameter {} is deprecated", key);
|
||||
was_deprecated = true;
|
||||
}
|
||||
|
||||
if was_deprecated {
|
||||
log::warn!("Read conduit documentation and check your configuration if any new configuration parameters should be adjusted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn false_fn() -> bool {
|
||||
@@ -58,24 +104,45 @@ fn true_fn() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_cache_capacity() -> u32 {
|
||||
fn default_db_cache_capacity() -> u32 {
|
||||
1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
fn default_sqlite_read_pool_size() -> usize {
|
||||
num_cpus::get().max(1)
|
||||
}
|
||||
|
||||
fn default_sqlite_wal_clean_second_interval() -> u32 {
|
||||
60
|
||||
}
|
||||
|
||||
fn default_sqlite_wal_clean_second_timeout() -> u32 {
|
||||
2
|
||||
}
|
||||
|
||||
fn default_max_request_size() -> u32 {
|
||||
20 * 1024 * 1024 // Default to 20 MB
|
||||
}
|
||||
|
||||
fn default_max_concurrent_requests() -> u16 {
|
||||
4
|
||||
100
|
||||
}
|
||||
|
||||
fn default_log() -> String {
|
||||
"info,state_res=warn,rocket=off,_=off,sled=off".to_owned()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "sled")]
|
||||
pub type Engine = abstraction::sled::Engine;
|
||||
|
||||
#[cfg(feature = "rocksdb")]
|
||||
pub type Engine = abstraction::rocksdb::Engine;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub type Engine = abstraction::sqlite::Engine;
|
||||
|
||||
pub struct Database {
|
||||
_db: Arc<Engine>,
|
||||
pub globals: globals::Globals,
|
||||
pub users: users::Users,
|
||||
pub uiaa: uiaa::Uiaa,
|
||||
@@ -88,7 +155,6 @@ pub struct Database {
|
||||
pub admin: admin::Admin,
|
||||
pub appservice: appservice::Appservice,
|
||||
pub pusher: pusher::PushData,
|
||||
pub _db: sled::Db,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
@@ -105,118 +171,226 @@ impl Database {
|
||||
}
|
||||
|
||||
/// Load an existing database or create a new one.
|
||||
pub async fn load_or_create(config: Config) -> Result<Self> {
|
||||
let db = sled::Config::default()
|
||||
.path(&config.database_path)
|
||||
.cache_capacity(config.cache_capacity as u64)
|
||||
.use_compression(true)
|
||||
.open()?;
|
||||
pub async fn load_or_create(config: Config) -> Result<Arc<TokioRwLock<Self>>> {
|
||||
let builder = Engine::open(&config)?;
|
||||
|
||||
info!("Opened sled database at {}", config.database_path);
|
||||
if config.max_request_size < 1024 {
|
||||
eprintln!("ERROR: Max request size is less than 1KB. Please increase it.");
|
||||
}
|
||||
|
||||
let (admin_sender, admin_receiver) = mpsc::unbounded();
|
||||
let (sending_sender, sending_receiver) = mpsc::unbounded();
|
||||
|
||||
let db = Self {
|
||||
let db = Arc::new(TokioRwLock::from(Self {
|
||||
_db: builder.clone(),
|
||||
users: users::Users {
|
||||
userid_password: db.open_tree("userid_password")?,
|
||||
userid_displayname: db.open_tree("userid_displayname")?,
|
||||
userid_avatarurl: db.open_tree("userid_avatarurl")?,
|
||||
userdeviceid_token: db.open_tree("userdeviceid_token")?,
|
||||
userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?,
|
||||
userid_devicelistversion: db.open_tree("userid_devicelistversion")?,
|
||||
token_userdeviceid: db.open_tree("token_userdeviceid")?,
|
||||
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?,
|
||||
userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?,
|
||||
keychangeid_userid: db.open_tree("keychangeid_userid")?,
|
||||
keyid_key: db.open_tree("keyid_key")?,
|
||||
userid_masterkeyid: db.open_tree("userid_masterkeyid")?,
|
||||
userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?,
|
||||
userid_usersigningkeyid: db.open_tree("userid_usersigningkeyid")?,
|
||||
todeviceid_events: db.open_tree("todeviceid_events")?,
|
||||
userid_password: builder.open_tree("userid_password")?,
|
||||
userid_displayname: builder.open_tree("userid_displayname")?,
|
||||
userid_avatarurl: builder.open_tree("userid_avatarurl")?,
|
||||
userdeviceid_token: builder.open_tree("userdeviceid_token")?,
|
||||
userdeviceid_metadata: builder.open_tree("userdeviceid_metadata")?,
|
||||
userid_devicelistversion: builder.open_tree("userid_devicelistversion")?,
|
||||
token_userdeviceid: builder.open_tree("token_userdeviceid")?,
|
||||
onetimekeyid_onetimekeys: builder.open_tree("onetimekeyid_onetimekeys")?,
|
||||
userid_lastonetimekeyupdate: builder.open_tree("userid_lastonetimekeyupdate")?,
|
||||
keychangeid_userid: builder.open_tree("keychangeid_userid")?,
|
||||
keyid_key: builder.open_tree("keyid_key")?,
|
||||
userid_masterkeyid: builder.open_tree("userid_masterkeyid")?,
|
||||
userid_selfsigningkeyid: builder.open_tree("userid_selfsigningkeyid")?,
|
||||
userid_usersigningkeyid: builder.open_tree("userid_usersigningkeyid")?,
|
||||
todeviceid_events: builder.open_tree("todeviceid_events")?,
|
||||
},
|
||||
uiaa: uiaa::Uiaa {
|
||||
userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo")?,
|
||||
userdevicesessionid_uiaainfo: builder.open_tree("userdevicesessionid_uiaainfo")?,
|
||||
userdevicesessionid_uiaarequest: builder
|
||||
.open_tree("userdevicesessionid_uiaarequest")?,
|
||||
},
|
||||
rooms: rooms::Rooms {
|
||||
edus: rooms::RoomEdus {
|
||||
readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?,
|
||||
roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt
|
||||
roomuserid_lastprivatereadupdate: db
|
||||
readreceiptid_readreceipt: builder.open_tree("readreceiptid_readreceipt")?,
|
||||
roomuserid_privateread: builder.open_tree("roomuserid_privateread")?, // "Private" read receipt
|
||||
roomuserid_lastprivatereadupdate: builder
|
||||
.open_tree("roomuserid_lastprivatereadupdate")?,
|
||||
typingid_userid: db.open_tree("typingid_userid")?,
|
||||
roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?,
|
||||
presenceid_presence: db.open_tree("presenceid_presence")?,
|
||||
userid_lastpresenceupdate: db.open_tree("userid_lastpresenceupdate")?,
|
||||
typingid_userid: builder.open_tree("typingid_userid")?,
|
||||
roomid_lasttypingupdate: builder.open_tree("roomid_lasttypingupdate")?,
|
||||
presenceid_presence: builder.open_tree("presenceid_presence")?,
|
||||
userid_lastpresenceupdate: builder.open_tree("userid_lastpresenceupdate")?,
|
||||
},
|
||||
pduid_pdu: db.open_tree("pduid_pdu")?,
|
||||
eventid_pduid: db.open_tree("eventid_pduid")?,
|
||||
roomid_pduleaves: db.open_tree("roomid_pduleaves")?,
|
||||
pduid_pdu: builder.open_tree("pduid_pdu")?,
|
||||
eventid_pduid: builder.open_tree("eventid_pduid")?,
|
||||
roomid_pduleaves: builder.open_tree("roomid_pduleaves")?,
|
||||
|
||||
alias_roomid: db.open_tree("alias_roomid")?,
|
||||
aliasid_alias: db.open_tree("aliasid_alias")?,
|
||||
publicroomids: db.open_tree("publicroomids")?,
|
||||
alias_roomid: builder.open_tree("alias_roomid")?,
|
||||
aliasid_alias: builder.open_tree("aliasid_alias")?,
|
||||
publicroomids: builder.open_tree("publicroomids")?,
|
||||
|
||||
tokenids: db.open_tree("tokenids")?,
|
||||
tokenids: builder.open_tree("tokenids")?,
|
||||
|
||||
roomserverids: db.open_tree("roomserverids")?,
|
||||
userroomid_joined: db.open_tree("userroomid_joined")?,
|
||||
roomuserid_joined: db.open_tree("roomuserid_joined")?,
|
||||
roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?,
|
||||
userroomid_invitestate: db.open_tree("userroomid_invitestate")?,
|
||||
roomuserid_invitecount: db.open_tree("roomuserid_invitecount")?,
|
||||
userroomid_leftstate: db.open_tree("userroomid_leftstate")?,
|
||||
roomuserid_leftcount: db.open_tree("roomuserid_leftcount")?,
|
||||
roomserverids: builder.open_tree("roomserverids")?,
|
||||
serverroomids: builder.open_tree("serverroomids")?,
|
||||
userroomid_joined: builder.open_tree("userroomid_joined")?,
|
||||
roomuserid_joined: builder.open_tree("roomuserid_joined")?,
|
||||
roomuseroncejoinedids: builder.open_tree("roomuseroncejoinedids")?,
|
||||
userroomid_invitestate: builder.open_tree("userroomid_invitestate")?,
|
||||
roomuserid_invitecount: builder.open_tree("roomuserid_invitecount")?,
|
||||
userroomid_leftstate: builder.open_tree("userroomid_leftstate")?,
|
||||
roomuserid_leftcount: builder.open_tree("roomuserid_leftcount")?,
|
||||
|
||||
userroomid_notificationcount: db.open_tree("userroomid_notificationcount")?,
|
||||
userroomid_highlightcount: db.open_tree("userroomid_highlightcount")?,
|
||||
userroomid_notificationcount: builder.open_tree("userroomid_notificationcount")?,
|
||||
userroomid_highlightcount: builder.open_tree("userroomid_highlightcount")?,
|
||||
|
||||
statekey_shortstatekey: db.open_tree("statekey_shortstatekey")?,
|
||||
stateid_shorteventid: db.open_tree("stateid_shorteventid")?,
|
||||
eventid_shorteventid: db.open_tree("eventid_shorteventid")?,
|
||||
shorteventid_eventid: db.open_tree("shorteventid_eventid")?,
|
||||
shorteventid_shortstatehash: db.open_tree("shorteventid_shortstatehash")?,
|
||||
roomid_shortstatehash: db.open_tree("roomid_shortstatehash")?,
|
||||
statehash_shortstatehash: db.open_tree("statehash_shortstatehash")?,
|
||||
statekey_shortstatekey: builder.open_tree("statekey_shortstatekey")?,
|
||||
stateid_shorteventid: builder.open_tree("stateid_shorteventid")?,
|
||||
eventid_shorteventid: builder.open_tree("eventid_shorteventid")?,
|
||||
shorteventid_eventid: builder.open_tree("shorteventid_eventid")?,
|
||||
shorteventid_shortstatehash: builder.open_tree("shorteventid_shortstatehash")?,
|
||||
roomid_shortstatehash: builder.open_tree("roomid_shortstatehash")?,
|
||||
statehash_shortstatehash: builder.open_tree("statehash_shortstatehash")?,
|
||||
|
||||
eventid_outlierpdu: db.open_tree("eventid_outlierpdu")?,
|
||||
prevevent_parent: db.open_tree("prevevent_parent")?,
|
||||
eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
|
||||
prevevent_parent: builder.open_tree("prevevent_parent")?,
|
||||
pdu_cache: RwLock::new(LruCache::new(10_000)),
|
||||
},
|
||||
account_data: account_data::AccountData {
|
||||
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
|
||||
roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
|
||||
},
|
||||
media: media::Media {
|
||||
mediaid_file: db.open_tree("mediaid_file")?,
|
||||
mediaid_file: builder.open_tree("mediaid_file")?,
|
||||
},
|
||||
key_backups: key_backups::KeyBackups {
|
||||
backupid_algorithm: db.open_tree("backupid_algorithm")?,
|
||||
backupid_etag: db.open_tree("backupid_etag")?,
|
||||
backupkeyid_backup: db.open_tree("backupkeyid_backup")?,
|
||||
backupid_algorithm: builder.open_tree("backupid_algorithm")?,
|
||||
backupid_etag: builder.open_tree("backupid_etag")?,
|
||||
backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
|
||||
},
|
||||
transaction_ids: transaction_ids::TransactionIds {
|
||||
userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?,
|
||||
userdevicetxnid_response: builder.open_tree("userdevicetxnid_response")?,
|
||||
},
|
||||
sending: sending::Sending {
|
||||
servernamepduids: db.open_tree("servernamepduids")?,
|
||||
servercurrentpdus: db.open_tree("servercurrentpdus")?,
|
||||
servername_educount: builder.open_tree("servername_educount")?,
|
||||
servernamepduids: builder.open_tree("servernamepduids")?,
|
||||
servercurrentevents: builder.open_tree("servercurrentevents")?,
|
||||
maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests as usize)),
|
||||
sender: sending_sender,
|
||||
},
|
||||
admin: admin::Admin {
|
||||
sender: admin_sender,
|
||||
},
|
||||
appservice: appservice::Appservice {
|
||||
cached_registrations: Arc::new(RwLock::new(HashMap::new())),
|
||||
id_appserviceregistrations: db.open_tree("id_appserviceregistrations")?,
|
||||
id_appserviceregistrations: builder.open_tree("id_appserviceregistrations")?,
|
||||
},
|
||||
pusher: pusher::PushData {
|
||||
senderkey_pusher: builder.open_tree("senderkey_pusher")?,
|
||||
},
|
||||
pusher: pusher::PushData::new(&db)?,
|
||||
globals: globals::Globals::load(
|
||||
db.open_tree("global")?,
|
||||
db.open_tree("servertimeout_signingkey")?,
|
||||
config,
|
||||
builder.open_tree("global")?,
|
||||
builder.open_tree("server_signingkeys")?,
|
||||
config.clone(),
|
||||
)?,
|
||||
_db: db,
|
||||
};
|
||||
}));
|
||||
|
||||
db.admin.start_handler(db.clone(), admin_receiver);
|
||||
{
|
||||
let db = db.read().await;
|
||||
// MIGRATIONS
|
||||
// TODO: database versions of new dbs should probably not be 0
|
||||
if db.globals.database_version()? < 1 {
|
||||
for (roomserverid, _) in db.rooms.roomserverids.iter() {
|
||||
let mut parts = roomserverid.split(|&b| b == 0xff);
|
||||
let room_id = parts.next().expect("split always returns one element");
|
||||
let servername = match parts.next() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
error!("Migration: Invalid roomserverid in db.");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut serverroomid = servername.to_vec();
|
||||
serverroomid.push(0xff);
|
||||
serverroomid.extend_from_slice(room_id);
|
||||
|
||||
db.rooms.serverroomids.insert(&serverroomid, &[])?;
|
||||
}
|
||||
|
||||
db.globals.bump_database_version(1)?;
|
||||
|
||||
println!("Migration: 0 -> 1 finished");
|
||||
}
|
||||
|
||||
if db.globals.database_version()? < 2 {
|
||||
// We accidentally inserted hashed versions of "" into the db instead of just ""
|
||||
for (userid, password) in db.users.userid_password.iter() {
|
||||
let password = utils::string_from_bytes(&password);
|
||||
|
||||
let empty_hashed_password = password.map_or(false, |password| {
|
||||
argon2::verify_encoded(&password, b"").unwrap_or(false)
|
||||
});
|
||||
|
||||
if empty_hashed_password {
|
||||
db.users.userid_password.insert(&userid, b"")?;
|
||||
}
|
||||
}
|
||||
|
||||
db.globals.bump_database_version(2)?;
|
||||
|
||||
println!("Migration: 1 -> 2 finished");
|
||||
}
|
||||
|
||||
if db.globals.database_version()? < 3 {
|
||||
// Move media to filesystem
|
||||
for (key, content) in db.media.mediaid_file.iter() {
|
||||
if content.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = db.globals.get_media_file(&key);
|
||||
let mut file = fs::File::create(path)?;
|
||||
file.write_all(&content)?;
|
||||
db.media.mediaid_file.insert(&key, &[])?;
|
||||
}
|
||||
|
||||
db.globals.bump_database_version(3)?;
|
||||
|
||||
println!("Migration: 2 -> 3 finished");
|
||||
}
|
||||
|
||||
if db.globals.database_version()? < 4 {
|
||||
// Add federated users to db as deactivated
|
||||
for our_user in db.users.iter() {
|
||||
let our_user = our_user?;
|
||||
if db.users.is_deactivated(&our_user)? {
|
||||
continue;
|
||||
}
|
||||
for room in db.rooms.rooms_joined(&our_user) {
|
||||
for user in db.rooms.room_members(&room?) {
|
||||
let user = user?;
|
||||
if user.server_name() != db.globals.server_name() {
|
||||
println!("Migration: Creating user {}", user);
|
||||
db.users.create(&user, None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.globals.bump_database_version(4)?;
|
||||
|
||||
println!("Migration: 3 -> 4 finished");
|
||||
}
|
||||
}
|
||||
|
||||
let guard = db.read().await;
|
||||
|
||||
// This data is probably outdated
|
||||
guard.rooms.edus.presenceid_presence.clear()?;
|
||||
|
||||
guard.admin.start_handler(Arc::clone(&db), admin_receiver);
|
||||
guard
|
||||
.sending
|
||||
.start_handler(Arc::clone(&db), sending_receiver);
|
||||
|
||||
drop(guard);
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
Self::start_wal_clean_task(&db, &config).await;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
@@ -230,7 +404,7 @@ impl Database {
|
||||
userdeviceid_prefix.extend_from_slice(device_id.as_bytes());
|
||||
userdeviceid_prefix.push(0xff);
|
||||
|
||||
let mut futures = futures::stream::FuturesUnordered::new();
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
// Return when *any* user changed his key
|
||||
// TODO: only send for user they share a room with
|
||||
@@ -305,13 +479,113 @@ impl Database {
|
||||
.watch_prefix(&userid_bytes),
|
||||
);
|
||||
|
||||
futures.push(Box::pin(self.globals.rotate.watch()));
|
||||
|
||||
// Wait until one of them finds something
|
||||
futures.next().await;
|
||||
}
|
||||
|
||||
pub async fn flush(&self) -> Result<()> {
|
||||
// noop while we don't use sled 1.0
|
||||
//self._db.flush_async().await?;
|
||||
Ok(())
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let res = self._db.flush();
|
||||
|
||||
log::debug!("flush: took {:?}", start.elapsed());
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub fn flush_wal(&self) -> Result<()> {
|
||||
self._db.flush_wal()
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub async fn start_wal_clean_task(lock: &Arc<TokioRwLock<Self>>, config: &Config) {
|
||||
use tokio::{
|
||||
select,
|
||||
signal::unix::{signal, SignalKind},
|
||||
time::{interval, timeout},
|
||||
};
|
||||
|
||||
use std::{
|
||||
sync::Weak,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
let weak: Weak<TokioRwLock<Database>> = Arc::downgrade(&lock);
|
||||
|
||||
let lock_timeout = Duration::from_secs(config.sqlite_wal_clean_second_timeout as u64);
|
||||
let timer_interval = Duration::from_secs(config.sqlite_wal_clean_second_interval as u64);
|
||||
let do_timer = config.sqlite_wal_clean_timer;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut i = interval(timer_interval);
|
||||
let mut s = signal(SignalKind::hangup()).unwrap();
|
||||
|
||||
loop {
|
||||
select! {
|
||||
_ = i.tick(), if do_timer => {
|
||||
log::info!(target: "wal-trunc", "Timer ticked")
|
||||
}
|
||||
_ = s.recv() => {
|
||||
log::info!(target: "wal-trunc", "Received SIGHUP")
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(arc) = Weak::upgrade(&weak) {
|
||||
log::info!(target: "wal-trunc", "Rotating sync helpers...");
|
||||
// This actually creates a very small race condition between firing this and trying to acquire the subsequent write lock.
|
||||
// Though it is not a huge deal if the write lock doesn't "catch", as it'll harmlessly time out.
|
||||
arc.read().await.globals.rotate.fire();
|
||||
|
||||
log::info!(target: "wal-trunc", "Locking...");
|
||||
let guard = {
|
||||
if let Ok(guard) = timeout(lock_timeout, arc.write()).await {
|
||||
guard
|
||||
} else {
|
||||
log::info!(target: "wal-trunc", "Lock failed in timeout, canceled.");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
log::info!(target: "wal-trunc", "Locked, flushing...");
|
||||
let start = Instant::now();
|
||||
if let Err(e) = guard.flush_wal() {
|
||||
log::error!(target: "wal-trunc", "Errored: {}", e);
|
||||
} else {
|
||||
log::info!(target: "wal-trunc", "Flushed in {:?}", start.elapsed());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DatabaseGuard(OwnedRwLockReadGuard<Database>);
|
||||
|
||||
impl Deref for DatabaseGuard {
|
||||
type Target = OwnedRwLockReadGuard<Database>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for DatabaseGuard {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> rocket::request::Outcome<Self, ()> {
|
||||
let db = try_outcome!(req.guard::<State<'_, Arc<TokioRwLock<Database>>>>().await);
|
||||
|
||||
Ok(DatabaseGuard(Arc::clone(&db).read_owned().await)).or_forward(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<DatabaseGuard> for OwnedRwLockReadGuard<Database> {
|
||||
fn into(self) -> DatabaseGuard {
|
||||
DatabaseGuard(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use super::Config;
|
||||
use crate::Result;
|
||||
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
#[cfg(feature = "rocksdb")]
|
||||
pub mod rocksdb;
|
||||
|
||||
#[cfg(feature = "sled")]
|
||||
pub mod sled;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub mod sqlite;
|
||||
|
||||
pub trait DatabaseEngine: Sized {
|
||||
fn open(config: &Config) -> Result<Arc<Self>>;
|
||||
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>>;
|
||||
fn flush(self: &Arc<Self>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait Tree: Send + Sync {
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()>;
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()>;
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a>;
|
||||
|
||||
fn iter_from<'a>(
|
||||
&'a self,
|
||||
from: &[u8],
|
||||
backwards: bool,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a>;
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>>;
|
||||
|
||||
fn scan_prefix<'a>(
|
||||
&'a self,
|
||||
prefix: Vec<u8>,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a>;
|
||||
|
||||
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
|
||||
|
||||
fn clear(&self) -> Result<()> {
|
||||
for (key, _) in self.iter() {
|
||||
self.remove(&key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use super::super::Config;
|
||||
use crate::{utils, Result};
|
||||
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
use super::{DatabaseEngine, Tree};
|
||||
|
||||
use std::{collections::BTreeMap, sync::RwLock};
|
||||
|
||||
pub struct Engine(rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>);
|
||||
|
||||
pub struct RocksDbEngineTree<'a> {
|
||||
db: Arc<Engine>,
|
||||
name: &'a str,
|
||||
watchers: RwLock<BTreeMap<Vec<u8>, Vec<tokio::sync::oneshot::Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl DatabaseEngine for Engine {
|
||||
fn open(config: &Config) -> Result<Arc<Self>> {
|
||||
let mut db_opts = rocksdb::Options::default();
|
||||
db_opts.create_if_missing(true);
|
||||
db_opts.set_max_open_files(16);
|
||||
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
|
||||
db_opts.set_compression_type(rocksdb::DBCompressionType::Snappy);
|
||||
db_opts.set_target_file_size_base(256 << 20);
|
||||
db_opts.set_write_buffer_size(256 << 20);
|
||||
|
||||
let mut block_based_options = rocksdb::BlockBasedOptions::default();
|
||||
block_based_options.set_block_size(512 << 10);
|
||||
db_opts.set_block_based_table_factory(&block_based_options);
|
||||
|
||||
let cfs = rocksdb::DBWithThreadMode::<rocksdb::MultiThreaded>::list_cf(
|
||||
&db_opts,
|
||||
&config.database_path,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut options = rocksdb::Options::default();
|
||||
options.set_merge_operator_associative("increment", utils::increment_rocksdb);
|
||||
|
||||
let db = rocksdb::DBWithThreadMode::<rocksdb::MultiThreaded>::open_cf_descriptors(
|
||||
&db_opts,
|
||||
&config.database_path,
|
||||
cfs.iter()
|
||||
.map(|name| rocksdb::ColumnFamilyDescriptor::new(name, options.clone())),
|
||||
)?;
|
||||
|
||||
Ok(Arc::new(Engine(db)))
|
||||
}
|
||||
|
||||
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>> {
|
||||
let mut options = rocksdb::Options::default();
|
||||
options.set_merge_operator_associative("increment", utils::increment_rocksdb);
|
||||
|
||||
// Create if it doesn't exist
|
||||
let _ = self.0.create_cf(name, &options);
|
||||
|
||||
Ok(Arc::new(RocksDbEngineTree {
|
||||
name,
|
||||
db: Arc::clone(self),
|
||||
watchers: RwLock::new(BTreeMap::new()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl RocksDbEngineTree<'_> {
|
||||
fn cf(&self) -> rocksdb::BoundColumnFamily<'_> {
|
||||
self.db.0.cf_handle(self.name).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree for RocksDbEngineTree<'_> {
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.db.0.get_cf(self.cf(), key)?)
|
||||
}
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
let watchers = self.watchers.read().unwrap();
|
||||
let mut triggered = Vec::new();
|
||||
|
||||
for length in 0..=key.len() {
|
||||
if watchers.contains_key(&key[..length]) {
|
||||
triggered.push(&key[..length]);
|
||||
}
|
||||
}
|
||||
|
||||
drop(watchers);
|
||||
|
||||
if !triggered.is_empty() {
|
||||
let mut watchers = self.watchers.write().unwrap();
|
||||
for prefix in triggered {
|
||||
if let Some(txs) = watchers.remove(prefix) {
|
||||
for tx in txs {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.db.0.put_cf(self.cf(), key, value)?)
|
||||
}
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()> {
|
||||
Ok(self.db.0.delete_cf(self.cf(), key)?)
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + Sync + 'a> {
|
||||
Box::new(
|
||||
self.db
|
||||
.0
|
||||
.iterator_cf(self.cf(), rocksdb::IteratorMode::Start),
|
||||
)
|
||||
}
|
||||
|
||||
fn iter_from<'a>(
|
||||
&'a self,
|
||||
from: &[u8],
|
||||
backwards: bool,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
|
||||
Box::new(self.db.0.iterator_cf(
|
||||
self.cf(),
|
||||
rocksdb::IteratorMode::From(
|
||||
from,
|
||||
if backwards {
|
||||
rocksdb::Direction::Reverse
|
||||
} else {
|
||||
rocksdb::Direction::Forward
|
||||
},
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
|
||||
let stats = rocksdb::perf::get_memory_usage_stats(Some(&[&self.db.0]), None).unwrap();
|
||||
dbg!(stats.mem_table_total);
|
||||
dbg!(stats.mem_table_unflushed);
|
||||
dbg!(stats.mem_table_readers_total);
|
||||
dbg!(stats.cache_total);
|
||||
// TODO: atomic?
|
||||
let old = self.get(key)?;
|
||||
let new = utils::increment(old.as_deref()).unwrap();
|
||||
self.insert(key, &new)?;
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
fn scan_prefix<'a>(
|
||||
&'a self,
|
||||
prefix: Vec<u8>,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a> {
|
||||
Box::new(
|
||||
self.db
|
||||
.0
|
||||
.iterator_cf(
|
||||
self.cf(),
|
||||
rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward),
|
||||
)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix)),
|
||||
)
|
||||
}
|
||||
|
||||
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
self.watchers
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(prefix.to_vec())
|
||||
.or_default()
|
||||
.push(tx);
|
||||
|
||||
Box::pin(async move {
|
||||
// Tx is never destroyed
|
||||
rx.await.unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use super::super::Config;
|
||||
use crate::{utils, Result};
|
||||
use log::warn;
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
use super::{DatabaseEngine, Tree};
|
||||
|
||||
pub struct Engine(sled::Db);
|
||||
|
||||
pub struct SledEngineTree(sled::Tree);
|
||||
|
||||
impl DatabaseEngine for Engine {
|
||||
fn open(config: &Config) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Engine(
|
||||
sled::Config::default()
|
||||
.path(&config.database_path)
|
||||
.cache_capacity(config.db_cache_capacity as u64)
|
||||
.use_compression(true)
|
||||
.open()?,
|
||||
)))
|
||||
}
|
||||
|
||||
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>> {
|
||||
Ok(Arc::new(SledEngineTree(self.0.open_tree(name)?)))
|
||||
}
|
||||
|
||||
fn flush(self: &Arc<Self>) -> Result<()> {
|
||||
Ok(()) // noop
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree for SledEngineTree {
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.0.get(key)?.map(|v| v.to_vec()))
|
||||
}
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
self.0.insert(key, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()> {
|
||||
self.0.remove(key)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a> {
|
||||
Box::new(
|
||||
self.0
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
if let Err(e) = &r {
|
||||
warn!("Error: {}", e);
|
||||
}
|
||||
r.ok()
|
||||
})
|
||||
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into())),
|
||||
)
|
||||
}
|
||||
|
||||
fn iter_from(
|
||||
&self,
|
||||
from: &[u8],
|
||||
backwards: bool,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send> {
|
||||
let iter = if backwards {
|
||||
self.0.range(..=from)
|
||||
} else {
|
||||
self.0.range(from..)
|
||||
};
|
||||
|
||||
let iter = iter
|
||||
.filter_map(|r| {
|
||||
if let Err(e) = &r {
|
||||
warn!("Error: {}", e);
|
||||
}
|
||||
r.ok()
|
||||
})
|
||||
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into()));
|
||||
|
||||
if backwards {
|
||||
Box::new(iter.rev())
|
||||
} else {
|
||||
Box::new(iter)
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
|
||||
Ok(self
|
||||
.0
|
||||
.update_and_fetch(key, utils::increment)
|
||||
.map(|o| o.expect("increment always sets a value").to_vec())?)
|
||||
}
|
||||
|
||||
fn scan_prefix<'a>(
|
||||
&'a self,
|
||||
prefix: Vec<u8>,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + Send + 'a> {
|
||||
let iter = self
|
||||
.0
|
||||
.scan_prefix(prefix)
|
||||
.filter_map(|r| {
|
||||
if let Err(e) = &r {
|
||||
warn!("Error: {}", e);
|
||||
}
|
||||
r.ok()
|
||||
})
|
||||
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into()));
|
||||
|
||||
Box::new(iter)
|
||||
}
|
||||
|
||||
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
let prefix = prefix.to_vec();
|
||||
Box::pin(async move {
|
||||
self.0.watch_prefix(prefix).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{database::Config, Result};
|
||||
|
||||
use super::{DatabaseEngine, Tree};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use crossbeam::channel::{bounded, Sender as ChannelSender};
|
||||
use parking_lot::{Mutex, MutexGuard, RwLock};
|
||||
use rusqlite::{params, Connection, DatabaseName::Main, OptionalExtension};
|
||||
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
// const SQL_CREATE_TABLE: &str =
|
||||
// "CREATE TABLE IF NOT EXISTS {} {{ \"key\" BLOB PRIMARY KEY, \"value\" BLOB NOT NULL }}";
|
||||
// const SQL_SELECT: &str = "SELECT value FROM {} WHERE key = ?";
|
||||
// const SQL_INSERT: &str = "INSERT OR REPLACE INTO {} (key, value) VALUES (?, ?)";
|
||||
// const SQL_DELETE: &str = "DELETE FROM {} WHERE key = ?";
|
||||
// const SQL_SELECT_ITER: &str = "SELECT key, value FROM {}";
|
||||
// const SQL_SELECT_PREFIX: &str = "SELECT key, value FROM {} WHERE key LIKE ?||'%' ORDER BY key ASC";
|
||||
// const SQL_SELECT_ITER_FROM_FORWARDS: &str = "SELECT key, value FROM {} WHERE key >= ? ORDER BY ASC";
|
||||
// const SQL_SELECT_ITER_FROM_BACKWARDS: &str =
|
||||
// "SELECT key, value FROM {} WHERE key <= ? ORDER BY DESC";
|
||||
|
||||
struct Pool {
|
||||
writer: Mutex<Connection>,
|
||||
readers: Vec<Mutex<Connection>>,
|
||||
spill_tracker: Arc<()>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
pub const MILLI: Duration = Duration::from_millis(1);
|
||||
|
||||
enum HoldingConn<'a> {
|
||||
FromGuard(MutexGuard<'a, Connection>),
|
||||
FromOwned(Connection, Arc<()>),
|
||||
}
|
||||
|
||||
impl<'a> Deref for HoldingConn<'a> {
|
||||
type Target = Connection;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
HoldingConn::FromGuard(guard) => guard.deref(),
|
||||
HoldingConn::FromOwned(conn, _) => conn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
fn new<P: AsRef<Path>>(path: P, num_readers: usize, cache_size: u32) -> Result<Self> {
|
||||
let writer = Mutex::new(Self::prepare_conn(&path, Some(cache_size))?);
|
||||
|
||||
let mut readers = Vec::new();
|
||||
|
||||
for _ in 0..num_readers {
|
||||
readers.push(Mutex::new(Self::prepare_conn(&path, Some(cache_size))?))
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
writer,
|
||||
readers,
|
||||
spill_tracker: Arc::new(()),
|
||||
path: path.as_ref().to_path_buf(),
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_conn<P: AsRef<Path>>(path: P, cache_size: Option<u32>) -> Result<Connection> {
|
||||
let conn = Connection::open(path)?;
|
||||
|
||||
conn.pragma_update(Some(Main), "journal_mode", &"WAL".to_owned())?;
|
||||
|
||||
// conn.pragma_update(Some(Main), "wal_autocheckpoint", &250)?;
|
||||
|
||||
// conn.pragma_update(Some(Main), "wal_checkpoint", &"FULL".to_owned())?;
|
||||
|
||||
conn.pragma_update(Some(Main), "synchronous", &"OFF".to_owned())?;
|
||||
|
||||
if let Some(cache_kib) = cache_size {
|
||||
conn.pragma_update(Some(Main), "cache_size", &(-Into::<i64>::into(cache_kib)))?;
|
||||
}
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
fn write_lock(&self) -> MutexGuard<'_, Connection> {
|
||||
self.writer.lock()
|
||||
}
|
||||
|
||||
fn read_lock(&self) -> HoldingConn<'_> {
|
||||
for r in &self.readers {
|
||||
if let Some(reader) = r.try_lock() {
|
||||
return HoldingConn::FromGuard(reader);
|
||||
}
|
||||
}
|
||||
|
||||
let spill_arc = self.spill_tracker.clone();
|
||||
let now_count = Arc::strong_count(&spill_arc) - 1 /* because one is held by the pool */;
|
||||
|
||||
log::warn!("read_lock: all readers locked, creating spillover reader...");
|
||||
|
||||
if now_count > 1 {
|
||||
log::warn!("read_lock: now {} spillover readers exist", now_count);
|
||||
}
|
||||
|
||||
let spilled = Self::prepare_conn(&self.path, None).unwrap();
|
||||
|
||||
return HoldingConn::FromOwned(spilled, spill_arc);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Engine {
|
||||
pool: Pool,
|
||||
}
|
||||
|
||||
impl DatabaseEngine for Engine {
|
||||
fn open(config: &Config) -> Result<Arc<Self>> {
|
||||
let pool = Pool::new(
|
||||
Path::new(&config.database_path).join("conduit.db"),
|
||||
config.sqlite_read_pool_size,
|
||||
config.db_cache_capacity / 1024, // bytes -> kb
|
||||
)?;
|
||||
|
||||
pool.write_lock()
|
||||
.execute("CREATE TABLE IF NOT EXISTS _noop (\"key\" INT)", params![])?;
|
||||
|
||||
let arc = Arc::new(Engine { pool });
|
||||
|
||||
Ok(arc)
|
||||
}
|
||||
|
||||
fn open_tree(self: &Arc<Self>, name: &str) -> Result<Arc<dyn Tree>> {
|
||||
self.pool.write_lock().execute(format!("CREATE TABLE IF NOT EXISTS {} ( \"key\" BLOB PRIMARY KEY, \"value\" BLOB NOT NULL )", name).as_str(), [])?;
|
||||
|
||||
Ok(Arc::new(SqliteTable {
|
||||
engine: Arc::clone(self),
|
||||
name: name.to_owned(),
|
||||
watchers: RwLock::new(BTreeMap::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
fn flush(self: &Arc<Self>) -> Result<()> {
|
||||
self.pool
|
||||
.write_lock()
|
||||
.execute_batch(
|
||||
"
|
||||
PRAGMA synchronous=FULL;
|
||||
BEGIN;
|
||||
DELETE FROM _noop;
|
||||
INSERT INTO _noop VALUES (1);
|
||||
COMMIT;
|
||||
PRAGMA synchronous=OFF;
|
||||
",
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn flush_wal(self: &Arc<Self>) -> Result<()> {
|
||||
self.pool
|
||||
.write_lock()
|
||||
.execute_batch(
|
||||
"
|
||||
PRAGMA synchronous=FULL; PRAGMA wal_checkpoint=TRUNCATE;
|
||||
BEGIN;
|
||||
DELETE FROM _noop;
|
||||
INSERT INTO _noop VALUES (1);
|
||||
COMMIT;
|
||||
PRAGMA wal_checkpoint=PASSIVE; PRAGMA synchronous=OFF;
|
||||
",
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SqliteTable {
|
||||
engine: Arc<Engine>,
|
||||
name: String,
|
||||
watchers: RwLock<BTreeMap<Vec<u8>, Vec<Sender<()>>>>,
|
||||
}
|
||||
|
||||
type TupleOfBytes = (Vec<u8>, Vec<u8>);
|
||||
|
||||
impl SqliteTable {
|
||||
fn get_with_guard(&self, guard: &Connection, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
Ok(guard
|
||||
.prepare(format!("SELECT value FROM {} WHERE key = ?", self.name).as_str())?
|
||||
.query_row([key], |row| row.get(0))
|
||||
.optional()?)
|
||||
}
|
||||
|
||||
fn insert_with_guard(&self, guard: &Connection, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
guard.execute(
|
||||
format!(
|
||||
"INSERT INTO {} (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
||||
self.name
|
||||
)
|
||||
.as_str(),
|
||||
[key, value],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _iter_from_thread<F>(&self, f: F) -> Box<dyn Iterator<Item = TupleOfBytes> + Send>
|
||||
where
|
||||
F: (for<'a> FnOnce(&'a Connection, ChannelSender<TupleOfBytes>)) + Send + 'static,
|
||||
{
|
||||
let (s, r) = bounded::<TupleOfBytes>(5);
|
||||
|
||||
let engine = self.engine.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let _ = f(&engine.pool.read_lock(), s);
|
||||
});
|
||||
|
||||
Box::new(r.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! iter_from_thread {
|
||||
($self:expr, $sql:expr, $param:expr) => {
|
||||
$self._iter_from_thread(move |guard, s| {
|
||||
let _ = guard
|
||||
.prepare($sql)
|
||||
.unwrap()
|
||||
.query_map($param, |row| Ok((row.get_unwrap(0), row.get_unwrap(1))))
|
||||
.unwrap()
|
||||
.map(|r| r.unwrap())
|
||||
.try_for_each(|bob| s.send(bob));
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
impl Tree for SqliteTable {
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
let guard = self.engine.pool.read_lock();
|
||||
|
||||
// let start = Instant::now();
|
||||
|
||||
let val = self.get_with_guard(&guard, key);
|
||||
|
||||
// debug!("get: took {:?}", start.elapsed());
|
||||
// debug!("get key: {:?}", &key)
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
let guard = self.engine.pool.write_lock();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
self.insert_with_guard(&guard, key, value)?;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed > MILLI {
|
||||
debug!("insert: took {:012?} : {}", elapsed, &self.name);
|
||||
}
|
||||
|
||||
drop(guard);
|
||||
|
||||
let watchers = self.watchers.read();
|
||||
let mut triggered = Vec::new();
|
||||
|
||||
for length in 0..=key.len() {
|
||||
if watchers.contains_key(&key[..length]) {
|
||||
triggered.push(&key[..length]);
|
||||
}
|
||||
}
|
||||
|
||||
drop(watchers);
|
||||
|
||||
if !triggered.is_empty() {
|
||||
let mut watchers = self.watchers.write();
|
||||
for prefix in triggered {
|
||||
if let Some(txs) = watchers.remove(prefix) {
|
||||
for tx in txs {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()> {
|
||||
let guard = self.engine.pool.write_lock();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
guard.execute(
|
||||
format!("DELETE FROM {} WHERE key = ?", self.name).as_str(),
|
||||
[key],
|
||||
)?;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
if elapsed > MILLI {
|
||||
debug!("remove: took {:012?} : {}", elapsed, &self.name);
|
||||
}
|
||||
// debug!("remove key: {:?}", &key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = TupleOfBytes> + Send + 'a> {
|
||||
let name = self.name.clone();
|
||||
iter_from_thread!(
|
||||
self,
|
||||
format!("SELECT key, value FROM {}", name).as_str(),
|
||||
params![]
|
||||
)
|
||||
}
|
||||
|
||||
fn iter_from<'a>(
|
||||
&'a self,
|
||||
from: &[u8],
|
||||
backwards: bool,
|
||||
) -> Box<dyn Iterator<Item = TupleOfBytes> + Send + 'a> {
|
||||
let name = self.name.clone();
|
||||
let from = from.to_vec(); // TODO change interface?
|
||||
if backwards {
|
||||
iter_from_thread!(
|
||||
self,
|
||||
format!(
|
||||
"SELECT key, value FROM {} WHERE key <= ? ORDER BY key DESC",
|
||||
name
|
||||
)
|
||||
.as_str(),
|
||||
[from]
|
||||
)
|
||||
} else {
|
||||
iter_from_thread!(
|
||||
self,
|
||||
format!(
|
||||
"SELECT key, value FROM {} WHERE key >= ? ORDER BY key ASC",
|
||||
name
|
||||
)
|
||||
.as_str(),
|
||||
[from]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
|
||||
let guard = self.engine.pool.write_lock();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let old = self.get_with_guard(&guard, key)?;
|
||||
|
||||
let new =
|
||||
crate::utils::increment(old.as_deref()).expect("utils::increment always returns Some");
|
||||
|
||||
self.insert_with_guard(&guard, key, &new)?;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
if elapsed > MILLI {
|
||||
debug!("increment: took {:012?} : {}", elapsed, &self.name);
|
||||
}
|
||||
// debug!("increment key: {:?}", &key);
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
fn scan_prefix<'a>(
|
||||
&'a self,
|
||||
prefix: Vec<u8>,
|
||||
) -> Box<dyn Iterator<Item = TupleOfBytes> + Send + 'a> {
|
||||
// let name = self.name.clone();
|
||||
// iter_from_thread!(
|
||||
// self,
|
||||
// format!(
|
||||
// "SELECT key, value FROM {} WHERE key BETWEEN ?1 AND ?1 || X'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ORDER BY key ASC",
|
||||
// name
|
||||
// )
|
||||
// .as_str(),
|
||||
// [prefix]
|
||||
// )
|
||||
Box::new(
|
||||
self.iter_from(&prefix, false)
|
||||
.take_while(move |(key, _)| key.starts_with(&prefix)),
|
||||
)
|
||||
}
|
||||
|
||||
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
self.watchers
|
||||
.write()
|
||||
.entry(prefix.to_vec())
|
||||
.or_default()
|
||||
.push(tx);
|
||||
|
||||
Box::pin(async move {
|
||||
// Tx is never destroyed
|
||||
rx.await.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
fn clear(&self) -> Result<()> {
|
||||
debug!("clear: running");
|
||||
self.engine
|
||||
.pool
|
||||
.write_lock()
|
||||
.execute(format!("DELETE FROM {}", self.name).as_str(), [])?;
|
||||
debug!("clear: ran");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// struct Pool<const NUM_READERS: usize> {
|
||||
// writer: Mutex<Connection>,
|
||||
// readers: [Mutex<Connection>; NUM_READERS],
|
||||
// }
|
||||
|
||||
// // then, to pick a reader:
|
||||
// for r in &pool.readers {
|
||||
// if let Ok(reader) = r.try_lock() {
|
||||
// // use reader
|
||||
// }
|
||||
// }
|
||||
// // none unlocked, pick the next reader
|
||||
// pool.readers[pool.counter.fetch_add(1, Relaxed) % NUM_READERS].lock()
|
||||
@@ -1,17 +1,17 @@
|
||||
use crate::{utils, Error, Result};
|
||||
use ruma::{
|
||||
api::client::error::ErrorKind,
|
||||
events::{AnyEvent as EduEvent, EventType},
|
||||
events::{AnyEphemeralRoomEvent, EventType},
|
||||
serde::Raw,
|
||||
RoomId, UserId,
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use sled::IVec;
|
||||
use std::{collections::HashMap, convert::TryFrom};
|
||||
use std::{collections::HashMap, convert::TryFrom, sync::Arc};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AccountData {
|
||||
pub(super) roomuserdataid_accountdata: sled::Tree, // RoomUserDataId = Room + User + Count + Type
|
||||
pub(super) roomuserdataid_accountdata: Arc<dyn Tree>, // RoomUserDataId = Room + User + Count + Type
|
||||
}
|
||||
|
||||
impl AccountData {
|
||||
@@ -34,9 +34,8 @@ impl AccountData {
|
||||
prefix.push(0xff);
|
||||
|
||||
// Remove old entry
|
||||
if let Some(previous) = self.find_event(room_id, user_id, &event_type) {
|
||||
let (old_key, _) = previous?;
|
||||
self.roomuserdataid_accountdata.remove(old_key)?;
|
||||
if let Some((old_key, _)) = self.find_event(room_id, user_id, &event_type)? {
|
||||
self.roomuserdataid_accountdata.remove(&old_key)?;
|
||||
}
|
||||
|
||||
let mut key = prefix;
|
||||
@@ -52,8 +51,10 @@ impl AccountData {
|
||||
));
|
||||
}
|
||||
|
||||
self.roomuserdataid_accountdata
|
||||
.insert(key, &*json.to_string())?;
|
||||
self.roomuserdataid_accountdata.insert(
|
||||
&key,
|
||||
&serde_json::to_vec(&json).expect("to_vec always works on json values"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -65,9 +66,8 @@ impl AccountData {
|
||||
user_id: &UserId,
|
||||
kind: EventType,
|
||||
) -> Result<Option<T>> {
|
||||
self.find_event(room_id, user_id, &kind)
|
||||
.map(|r| {
|
||||
let (_, v) = r?;
|
||||
self.find_event(room_id, user_id, &kind)?
|
||||
.map(|(_, v)| {
|
||||
serde_json::from_slice(&v).map_err(|_| Error::bad_database("could not deserialize"))
|
||||
})
|
||||
.transpose()
|
||||
@@ -80,7 +80,7 @@ impl AccountData {
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
since: u64,
|
||||
) -> Result<HashMap<EventType, Raw<EduEvent>>> {
|
||||
) -> Result<HashMap<EventType, Raw<AnyEphemeralRoomEvent>>> {
|
||||
let mut userdata = HashMap::new();
|
||||
|
||||
let mut prefix = room_id
|
||||
@@ -98,8 +98,7 @@ impl AccountData {
|
||||
|
||||
for r in self
|
||||
.roomuserdataid_accountdata
|
||||
.range(&*first_possible..)
|
||||
.filter_map(|r| r.ok())
|
||||
.iter_from(&first_possible, false)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(k, v)| {
|
||||
Ok::<_, Error>((
|
||||
@@ -110,7 +109,7 @@ impl AccountData {
|
||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
||||
serde_json::from_slice::<Raw<EduEvent>>(&v).map_err(|_| {
|
||||
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
||||
Error::bad_database("Database contains invalid account data.")
|
||||
})?,
|
||||
))
|
||||
@@ -128,7 +127,7 @@ impl AccountData {
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
kind: &EventType,
|
||||
) -> Option<Result<(IVec, IVec)>> {
|
||||
) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
|
||||
let mut prefix = room_id
|
||||
.map(|r| r.to_string())
|
||||
.unwrap_or_default()
|
||||
@@ -137,23 +136,21 @@ impl AccountData {
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&user_id.as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut last_possible_key = prefix.clone();
|
||||
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
|
||||
|
||||
let kind = kind.clone();
|
||||
|
||||
self.roomuserdataid_accountdata
|
||||
.scan_prefix(prefix)
|
||||
.rev()
|
||||
.find(move |r| {
|
||||
r.as_ref()
|
||||
.map(|(k, _)| {
|
||||
k.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.map(|current_event_type| {
|
||||
current_event_type == kind.as_ref().as_bytes()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
Ok(self
|
||||
.roomuserdataid_accountdata
|
||||
.iter_from(&last_possible_key, true)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.find(move |(k, _)| {
|
||||
k.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.map(|current_event_type| current_event_type == kind.as_ref().as_bytes())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|r| Ok(r?))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+70
-39
@@ -1,12 +1,16 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::pdu::PduBuilder;
|
||||
use crate::{pdu::PduBuilder, Database};
|
||||
use log::warn;
|
||||
use rocket::futures::{channel::mpsc, stream::StreamExt};
|
||||
use ruma::{
|
||||
events::{room::message, EventType},
|
||||
UserId,
|
||||
};
|
||||
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
|
||||
|
||||
pub enum AdminCommand {
|
||||
RegisterAppservice(serde_yaml::Value),
|
||||
@@ -22,70 +26,97 @@ pub struct Admin {
|
||||
impl Admin {
|
||||
pub fn start_handler(
|
||||
&self,
|
||||
db: super::Database,
|
||||
db: Arc<RwLock<Database>>,
|
||||
mut receiver: mpsc::UnboundedReceiver<AdminCommand>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
// TODO: Use futures when we have long admin commands
|
||||
//let mut futures = FuturesUnordered::new();
|
||||
|
||||
let conduit_user = UserId::try_from(format!("@conduit:{}", db.globals.server_name()))
|
||||
.expect("@conduit:server_name is valid");
|
||||
let guard = db.read().await;
|
||||
|
||||
let conduit_room = db
|
||||
let conduit_user =
|
||||
UserId::try_from(format!("@conduit:{}", guard.globals.server_name()))
|
||||
.expect("@conduit:server_name is valid");
|
||||
|
||||
let conduit_room = guard
|
||||
.rooms
|
||||
.id_from_alias(
|
||||
&format!("#admins:{}", db.globals.server_name())
|
||||
&format!("#admins:{}", guard.globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid room alias"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if conduit_room.is_none() {
|
||||
warn!("Conduit instance does not have an #admins room. Logging to that room will not work. Restart Conduit after creating a user to fix this.");
|
||||
}
|
||||
|
||||
let send_message = |message: message::MessageEventContent| {
|
||||
if let Some(conduit_room) = &conduit_room {
|
||||
db.rooms
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMessage,
|
||||
content: serde_json::to_value(message)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&conduit_room,
|
||||
&db,
|
||||
)
|
||||
.unwrap();
|
||||
let conduit_room = match conduit_room {
|
||||
None => {
|
||||
warn!("Conduit instance does not have an #admins room. Logging to that room will not work. Restart Conduit after creating a user to fix this.");
|
||||
return;
|
||||
}
|
||||
Some(r) => r,
|
||||
};
|
||||
|
||||
drop(guard);
|
||||
|
||||
let send_message = |message: message::MessageEventContent,
|
||||
guard: RwLockReadGuard<'_, Database>,
|
||||
mutex_lock: &MutexGuard<'_, ()>| {
|
||||
guard
|
||||
.rooms
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMessage,
|
||||
content: serde_json::to_value(message)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&conduit_room,
|
||||
&guard,
|
||||
mutex_lock,
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = receiver.next() => {
|
||||
let guard = db.read().await;
|
||||
let mutex = Arc::clone(
|
||||
guard.globals
|
||||
.roomid_mutex
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(conduit_room.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let mutex_lock = mutex.lock().await;
|
||||
|
||||
match event {
|
||||
AdminCommand::RegisterAppservice(yaml) => {
|
||||
db.appservice.register_appservice(yaml).unwrap(); // TODO handle error
|
||||
guard.appservice.register_appservice(yaml).unwrap(); // TODO handle error
|
||||
}
|
||||
AdminCommand::ListAppservices => {
|
||||
let appservices = db.appservice.iter_ids().collect::<Vec<_>>();
|
||||
let count = appservices.len();
|
||||
let output = format!(
|
||||
"Appservices ({}): {}",
|
||||
count,
|
||||
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
|
||||
);
|
||||
send_message(message::MessageEventContent::text_plain(output));
|
||||
if let Ok(appservices) = guard.appservice.iter_ids().map(|ids| ids.collect::<Vec<_>>()) {
|
||||
let count = appservices.len();
|
||||
let output = format!(
|
||||
"Appservices ({}): {}",
|
||||
count,
|
||||
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
|
||||
);
|
||||
send_message(message::MessageEventContent::text_plain(output), guard, &mutex_lock);
|
||||
} else {
|
||||
send_message(message::MessageEventContent::text_plain("Failed to get appservices."), guard, &mutex_lock);
|
||||
}
|
||||
}
|
||||
AdminCommand::SendMessage(message) => {
|
||||
send_message(message);
|
||||
send_message(message, guard, &mutex_lock);
|
||||
}
|
||||
}
|
||||
|
||||
drop(mutex_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +124,6 @@ impl Admin {
|
||||
}
|
||||
|
||||
pub fn send(&self, command: AdminCommand) {
|
||||
self.sender.unbounded_send(command).unwrap()
|
||||
self.sender.unbounded_send(command).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+22
-19
@@ -4,18 +4,21 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
use super::abstraction::Tree;
|
||||
|
||||
pub struct Appservice {
|
||||
pub(super) cached_registrations: Arc<RwLock<HashMap<String, serde_yaml::Value>>>,
|
||||
pub(super) id_appserviceregistrations: sled::Tree,
|
||||
pub(super) id_appserviceregistrations: Arc<dyn Tree>,
|
||||
}
|
||||
|
||||
impl Appservice {
|
||||
pub fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<()> {
|
||||
// TODO: Rumaify
|
||||
let id = yaml.get("id").unwrap().as_str().unwrap();
|
||||
self.id_appserviceregistrations
|
||||
.insert(id, serde_yaml::to_string(&yaml).unwrap().as_bytes())?;
|
||||
self.id_appserviceregistrations.insert(
|
||||
id.as_bytes(),
|
||||
serde_yaml::to_string(&yaml).unwrap().as_bytes(),
|
||||
)?;
|
||||
self.cached_registrations
|
||||
.write()
|
||||
.unwrap()
|
||||
@@ -31,37 +34,37 @@ impl Appservice {
|
||||
.get(id)
|
||||
.map_or_else(
|
||||
|| {
|
||||
Ok(self
|
||||
.id_appserviceregistrations
|
||||
.get(id)?
|
||||
self.id_appserviceregistrations
|
||||
.get(id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
Ok::<_, Error>(serde_yaml::from_slice(&bytes).map_err(|_| {
|
||||
serde_yaml::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid registration bytes in id_appserviceregistrations.",
|
||||
)
|
||||
})?)
|
||||
})
|
||||
})
|
||||
.transpose()?)
|
||||
.transpose()
|
||||
},
|
||||
|r| Ok(Some(r.clone())),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn iter_ids(&self) -> impl Iterator<Item = Result<String>> {
|
||||
self.id_appserviceregistrations.iter().keys().map(|id| {
|
||||
Ok(utils::string_from_bytes(&id?).map_err(|_| {
|
||||
Error::bad_database("Invalid id bytes in id_appserviceregistrations.")
|
||||
})?)
|
||||
})
|
||||
pub fn iter_ids(&self) -> Result<impl Iterator<Item = Result<String>> + Send + '_> {
|
||||
Ok(self.id_appserviceregistrations.iter().map(|(id, _)| {
|
||||
utils::string_from_bytes(&id)
|
||||
.map_err(|_| Error::bad_database("Invalid id bytes in id_appserviceregistrations."))
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn iter_all(&self) -> impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ {
|
||||
self.iter_ids().filter_map(|id| id.ok()).map(move |id| {
|
||||
pub fn iter_all(
|
||||
&self,
|
||||
) -> Result<impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ + Send> {
|
||||
Ok(self.iter_ids()?.filter_map(|id| id.ok()).map(move |id| {
|
||||
Ok((
|
||||
id.clone(),
|
||||
self.get_registration(&id)?
|
||||
.expect("iter_ids only returns appservices that exist"),
|
||||
))
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+152
-70
@@ -1,32 +1,56 @@
|
||||
use crate::{database::Config, utils, Error, Result};
|
||||
use crate::{database::Config, utils, ConduitResult, Error, Result};
|
||||
use log::{error, info};
|
||||
use ruma::{
|
||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
ServerName, ServerSigningKeyId,
|
||||
api::{
|
||||
client::r0::sync::sync_events,
|
||||
federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
},
|
||||
DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, ServerName, ServerSigningKeyId, UserId,
|
||||
};
|
||||
use rustls::{ServerCertVerifier, WebPKIVerifier};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fs,
|
||||
future::Future,
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::{broadcast, Mutex, Semaphore};
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
pub const COUNTER: &str = "c";
|
||||
use super::abstraction::Tree;
|
||||
|
||||
pub const COUNTER: &[u8] = b"c";
|
||||
|
||||
type WellKnownMap = HashMap<Box<ServerName>, (String, String)>;
|
||||
type TlsNameMap = HashMap<String, webpki::DNSName>;
|
||||
#[derive(Clone)]
|
||||
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
||||
pub struct Globals {
|
||||
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
|
||||
pub tls_name_override: Arc<RwLock<TlsNameMap>>,
|
||||
pub(super) globals: sled::Tree,
|
||||
pub(super) globals: Arc<dyn Tree>,
|
||||
config: Config,
|
||||
keypair: Arc<ruma::signatures::Ed25519KeyPair>,
|
||||
reqwest_client: reqwest::Client,
|
||||
dns_resolver: TokioAsyncResolver,
|
||||
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
|
||||
pub(super) servertimeout_signingkey: sled::Tree, // ServerName + Timeout Timestamp -> algorithm:key + pubkey
|
||||
pub(super) server_signingkeys: Arc<dyn Tree>,
|
||||
pub bad_event_ratelimiter: Arc<RwLock<BTreeMap<EventId, RateLimitState>>>,
|
||||
pub bad_signature_ratelimiter: Arc<RwLock<BTreeMap<Vec<String>, RateLimitState>>>,
|
||||
pub servername_ratelimiter: Arc<RwLock<BTreeMap<Box<ServerName>, Arc<Semaphore>>>>,
|
||||
pub roomid_mutex: RwLock<BTreeMap<RoomId, Arc<Mutex<()>>>>,
|
||||
pub roomid_mutex_federation: RwLock<BTreeMap<RoomId, Arc<Mutex<()>>>>, // this lock will be held longer
|
||||
pub sync_receivers: RwLock<
|
||||
BTreeMap<
|
||||
(UserId, Box<DeviceId>),
|
||||
(
|
||||
Option<String>,
|
||||
tokio::sync::watch::Receiver<Option<ConduitResult<sync_events::Response>>>,
|
||||
), // since, rx
|
||||
>,
|
||||
>,
|
||||
pub rotate: RotationHandler,
|
||||
}
|
||||
|
||||
struct MatrixServerVerifier {
|
||||
@@ -62,17 +86,44 @@ impl ServerCertVerifier for MatrixServerVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RotationHandler(broadcast::Sender<()>, broadcast::Receiver<()>);
|
||||
|
||||
impl RotationHandler {
|
||||
pub fn new() -> Self {
|
||||
let (s, r) = broadcast::channel::<()>(1);
|
||||
|
||||
Self(s, r)
|
||||
}
|
||||
|
||||
pub fn watch(&self) -> impl Future<Output = ()> {
|
||||
let mut r = self.0.subscribe();
|
||||
|
||||
async move {
|
||||
let _ = r.recv().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fire(&self) {
|
||||
let _ = self.0.send(());
|
||||
}
|
||||
}
|
||||
|
||||
impl Globals {
|
||||
pub fn load(
|
||||
globals: sled::Tree,
|
||||
servertimeout_signingkey: sled::Tree,
|
||||
globals: Arc<dyn Tree>,
|
||||
server_signingkeys: Arc<dyn Tree>,
|
||||
config: Config,
|
||||
) -> Result<Self> {
|
||||
let bytes = &*globals
|
||||
.update_and_fetch("keypair", utils::generate_keypair)?
|
||||
.expect("utils::generate_keypair always returns Some");
|
||||
let keypair_bytes = globals.get(b"keypair")?.map_or_else(
|
||||
|| {
|
||||
let keypair = utils::generate_keypair();
|
||||
globals.insert(b"keypair", &keypair)?;
|
||||
Ok::<_, Error>(keypair)
|
||||
},
|
||||
|s| Ok(s.to_vec()),
|
||||
)?;
|
||||
|
||||
let mut parts = bytes.splitn(2, |&b| b == 0xff);
|
||||
let mut parts = keypair_bytes.splitn(2, |&b| b == 0xff);
|
||||
|
||||
let keypair = utils::string_from_bytes(
|
||||
// 1. version
|
||||
@@ -89,7 +140,7 @@ impl Globals {
|
||||
.map(|key| (version, key))
|
||||
})
|
||||
.and_then(|(version, key)| {
|
||||
ruma::signatures::Ed25519KeyPair::new(&key, version)
|
||||
ruma::signatures::Ed25519KeyPair::from_der(&key, version)
|
||||
.map_err(|_| Error::bad_database("Private or public keys are invalid."))
|
||||
});
|
||||
|
||||
@@ -97,7 +148,7 @@ impl Globals {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
error!("Keypair invalid. Deleting...");
|
||||
globals.remove("keypair")?;
|
||||
globals.remove(b"keypair")?;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
@@ -112,20 +163,22 @@ impl Globals {
|
||||
tlsconfig.root_store =
|
||||
rustls_native_certs::load_native_certs().expect("Error loading system certificates");
|
||||
|
||||
let reqwest_client = reqwest::Client::builder()
|
||||
let mut reqwest_client_builder = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(30))
|
||||
.timeout(Duration::from_secs(60 * 3))
|
||||
.pool_max_idle_per_host(1)
|
||||
.use_preconfigured_tls(tlsconfig)
|
||||
.build()
|
||||
.unwrap();
|
||||
.use_preconfigured_tls(tlsconfig);
|
||||
if let Some(proxy) = config.proxy.to_proxy()? {
|
||||
reqwest_client_builder = reqwest_client_builder.proxy(proxy);
|
||||
}
|
||||
let reqwest_client = reqwest_client_builder.build().unwrap();
|
||||
|
||||
let jwt_decoding_key = config
|
||||
.jwt_secret
|
||||
.as_ref()
|
||||
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static());
|
||||
|
||||
Ok(Self {
|
||||
let s = Self {
|
||||
globals,
|
||||
config,
|
||||
keypair: Arc::new(keypair),
|
||||
@@ -135,9 +188,20 @@ impl Globals {
|
||||
})?,
|
||||
actual_destination_cache: Arc::new(RwLock::new(WellKnownMap::new())),
|
||||
tls_name_override,
|
||||
servertimeout_signingkey,
|
||||
server_signingkeys,
|
||||
jwt_decoding_key,
|
||||
})
|
||||
bad_event_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
bad_signature_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
servername_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
roomid_mutex: RwLock::new(BTreeMap::new()),
|
||||
roomid_mutex_federation: RwLock::new(BTreeMap::new()),
|
||||
sync_receivers: RwLock::new(BTreeMap::new()),
|
||||
rotate: RotationHandler::new(),
|
||||
};
|
||||
|
||||
fs::create_dir_all(s.get_media_folder())?;
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Returns this server's keypair.
|
||||
@@ -151,19 +215,14 @@ impl Globals {
|
||||
}
|
||||
|
||||
pub fn next_count(&self) -> Result<u64> {
|
||||
Ok(utils::u64_from_bytes(
|
||||
&self
|
||||
.globals
|
||||
.update_and_fetch(COUNTER, utils::increment)?
|
||||
.expect("utils::increment will always put in a value"),
|
||||
)
|
||||
.map_err(|_| Error::bad_database("Count has invalid bytes."))?)
|
||||
utils::u64_from_bytes(&self.globals.increment(COUNTER)?)
|
||||
.map_err(|_| Error::bad_database("Count has invalid bytes."))
|
||||
}
|
||||
|
||||
pub fn current_count(&self) -> Result<u64> {
|
||||
self.globals.get(COUNTER)?.map_or(Ok(0_u64), |bytes| {
|
||||
Ok(utils::u64_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Count has invalid bytes."))?)
|
||||
utils::u64_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Count has invalid bytes."))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,30 +262,29 @@ impl Globals {
|
||||
/// Remove the outdated keys and insert the new ones.
|
||||
///
|
||||
/// This doesn't actually check that the keys provided are newer than the old set.
|
||||
pub fn add_signing_key(&self, origin: &ServerName, keys: &ServerSigningKeys) -> Result<()> {
|
||||
let mut key1 = origin.as_bytes().to_vec();
|
||||
key1.push(0xff);
|
||||
pub fn add_signing_key(&self, origin: &ServerName, new_keys: ServerSigningKeys) -> Result<()> {
|
||||
// Not atomic, but this is not critical
|
||||
let signingkeys = self.server_signingkeys.get(origin.as_bytes())?;
|
||||
|
||||
let mut key2 = key1.clone();
|
||||
let mut keys = signingkeys
|
||||
.and_then(|keys| serde_json::from_slice(&keys).ok())
|
||||
.unwrap_or_else(|| {
|
||||
// Just insert "now", it doesn't matter
|
||||
ServerSigningKeys::new(origin.to_owned(), MilliSecondsSinceUnixEpoch::now())
|
||||
});
|
||||
|
||||
let ts = keys
|
||||
.valid_until_ts
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time is valid")
|
||||
.as_millis() as u64;
|
||||
let ServerSigningKeys {
|
||||
verify_keys,
|
||||
old_verify_keys,
|
||||
..
|
||||
} = new_keys;
|
||||
|
||||
key1.extend_from_slice(&ts.to_be_bytes());
|
||||
key2.extend_from_slice(&(ts + 1).to_be_bytes());
|
||||
keys.verify_keys.extend(verify_keys.into_iter());
|
||||
keys.old_verify_keys.extend(old_verify_keys.into_iter());
|
||||
|
||||
self.servertimeout_signingkey.insert(
|
||||
key1,
|
||||
serde_json::to_vec(&keys.verify_keys).expect("ServerSigningKeys are a valid string"),
|
||||
)?;
|
||||
|
||||
self.servertimeout_signingkey.insert(
|
||||
key2,
|
||||
serde_json::to_vec(&keys.old_verify_keys)
|
||||
.expect("ServerSigningKeys are a valid string"),
|
||||
self.server_signingkeys.insert(
|
||||
origin.as_bytes(),
|
||||
&serde_json::to_vec(&keys).expect("serversigningkeys can be serialized"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -237,25 +295,49 @@ impl Globals {
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
|
||||
let mut response = BTreeMap::new();
|
||||
let signingkeys = self
|
||||
.server_signingkeys
|
||||
.get(origin.as_bytes())?
|
||||
.and_then(|bytes| serde_json::from_slice::<ServerSigningKeys>(&bytes).ok())
|
||||
.map(|keys| {
|
||||
let mut tree = keys.verify_keys;
|
||||
tree.extend(
|
||||
keys.old_verify_keys
|
||||
.into_iter()
|
||||
.map(|old| (old.0, VerifyKey::new(old.1.key))),
|
||||
);
|
||||
tree
|
||||
})
|
||||
.unwrap_or_else(BTreeMap::new);
|
||||
|
||||
let now = crate::utils::millis_since_unix_epoch();
|
||||
Ok(signingkeys)
|
||||
}
|
||||
|
||||
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
|
||||
let (k, bytes) = item?;
|
||||
let valid_until = k
|
||||
.splitn(2, |&b| b == 0xff)
|
||||
.nth(1)
|
||||
.map(crate::utils::u64_from_bytes)
|
||||
.ok_or_else(|| Error::bad_database("Invalid signing keys."))?
|
||||
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?;
|
||||
// If these keys are still valid use em!
|
||||
if valid_until > now {
|
||||
let btree: BTreeMap<_, _> = serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"))?;
|
||||
response.extend(btree);
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
pub fn database_version(&self) -> Result<u64> {
|
||||
self.globals.get(b"version")?.map_or(Ok(0), |version| {
|
||||
utils::u64_from_bytes(&version)
|
||||
.map_err(|_| Error::bad_database("Database version id is invalid."))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bump_database_version(&self, new_version: u64) -> Result<()> {
|
||||
self.globals
|
||||
.insert(b"version", &new_version.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_media_folder(&self) -> PathBuf {
|
||||
let mut r = PathBuf::new();
|
||||
r.push(self.config.database_path.clone());
|
||||
r.push("media");
|
||||
r
|
||||
}
|
||||
|
||||
pub fn get_media_file(&self, key: &[u8]) -> PathBuf {
|
||||
let mut r = PathBuf::new();
|
||||
r.push(self.config.database_path.clone());
|
||||
r.push("media");
|
||||
r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
+65
-75
@@ -6,13 +6,14 @@ use ruma::{
|
||||
},
|
||||
RoomId, UserId,
|
||||
};
|
||||
use std::{collections::BTreeMap, convert::TryFrom};
|
||||
use std::{collections::BTreeMap, convert::TryFrom, sync::Arc};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyBackups {
|
||||
pub(super) backupid_algorithm: sled::Tree, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupid_etag: sled::Tree, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupkeyid_backup: sled::Tree, // BackupKeyId = UserId + Version + RoomId + SessionId
|
||||
pub(super) backupid_algorithm: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupid_etag: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupkeyid_backup: Arc<dyn Tree>, // BackupKeyId = UserId + Version + RoomId + SessionId
|
||||
}
|
||||
|
||||
impl KeyBackups {
|
||||
@@ -30,8 +31,7 @@ impl KeyBackups {
|
||||
|
||||
self.backupid_algorithm.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(backup_metadata)
|
||||
.expect("BackupAlgorithm::to_string always works"),
|
||||
&serde_json::to_vec(backup_metadata).expect("BackupAlgorithm::to_vec always works"),
|
||||
)?;
|
||||
self.backupid_etag
|
||||
.insert(&key, &globals.next_count()?.to_be_bytes())?;
|
||||
@@ -48,13 +48,8 @@ impl KeyBackups {
|
||||
|
||||
key.push(0xff);
|
||||
|
||||
for outdated_key in self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(&key)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
self.backupkeyid_backup.remove(outdated_key)?;
|
||||
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
|
||||
self.backupkeyid_backup.remove(&outdated_key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -80,8 +75,9 @@ impl KeyBackups {
|
||||
|
||||
self.backupid_algorithm.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(backup_metadata)
|
||||
.expect("BackupAlgorithm::to_string always works"),
|
||||
&serde_json::to_string(backup_metadata)
|
||||
.expect("BackupAlgorithm::to_string always works")
|
||||
.as_bytes(),
|
||||
)?;
|
||||
self.backupid_etag
|
||||
.insert(&key, &globals.next_count()?.to_be_bytes())?;
|
||||
@@ -91,11 +87,14 @@ impl KeyBackups {
|
||||
pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
let mut last_possible_key = prefix.clone();
|
||||
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
|
||||
|
||||
self.backupid_algorithm
|
||||
.scan_prefix(&prefix)
|
||||
.last()
|
||||
.map_or(Ok(None), |r| {
|
||||
let (key, value) = r?;
|
||||
.iter_from(&last_possible_key, true)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.next()
|
||||
.map_or(Ok(None), |(key, value)| {
|
||||
let version = utils::string_from_bytes(
|
||||
key.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
@@ -117,10 +116,12 @@ impl KeyBackups {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(version.as_bytes());
|
||||
|
||||
self.backupid_algorithm.get(key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))?)
|
||||
})
|
||||
self.backupid_algorithm
|
||||
.get(&key)?
|
||||
.map_or(Ok(None), |bytes| {
|
||||
serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_key(
|
||||
@@ -153,7 +154,7 @@ impl KeyBackups {
|
||||
|
||||
self.backupkeyid_backup.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&key_data).expect("KeyBackupData::to_string always works"),
|
||||
&serde_json::to_vec(&key_data).expect("KeyBackupData::to_vec always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -164,7 +165,7 @@ impl KeyBackups {
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
|
||||
Ok(self.backupkeyid_backup.scan_prefix(&prefix).count())
|
||||
Ok(self.backupkeyid_backup.scan_prefix(prefix).count())
|
||||
}
|
||||
|
||||
pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> {
|
||||
@@ -194,33 +195,37 @@ impl KeyBackups {
|
||||
|
||||
let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new();
|
||||
|
||||
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| {
|
||||
let (key, value) = r?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
for result in self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(prefix)
|
||||
.map(|(key, value)| {
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let session_id = utils::string_from_bytes(
|
||||
&parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup session_id is invalid."))?;
|
||||
let session_id =
|
||||
utils::string_from_bytes(&parts.next().ok_or_else(|| {
|
||||
Error::bad_database("backupkeyid_backup key is invalid.")
|
||||
})?)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("backupkeyid_backup session_id is invalid.")
|
||||
})?;
|
||||
|
||||
let room_id = RoomId::try_from(
|
||||
utils::string_from_bytes(
|
||||
&parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?,
|
||||
let room_id = RoomId::try_from(
|
||||
utils::string_from_bytes(&parts.next().ok_or_else(|| {
|
||||
Error::bad_database("backupkeyid_backup key is invalid.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?;
|
||||
.map_err(|_| {
|
||||
Error::bad_database("backupkeyid_backup room_id is invalid room id.")
|
||||
})?;
|
||||
|
||||
let key_data = serde_json::from_slice(&value).map_err(|_| {
|
||||
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
|
||||
})?;
|
||||
let key_data = serde_json::from_slice(&value).map_err(|_| {
|
||||
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
|
||||
})?;
|
||||
|
||||
Ok::<_, Error>((room_id, session_id, key_data))
|
||||
}) {
|
||||
Ok::<_, Error>((room_id, session_id, key_data))
|
||||
})
|
||||
{
|
||||
let (room_id, session_id, key_data) = result?;
|
||||
rooms
|
||||
.entry(room_id)
|
||||
@@ -239,7 +244,7 @@ impl KeyBackups {
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
) -> BTreeMap<String, KeyBackupData> {
|
||||
) -> Result<BTreeMap<String, KeyBackupData>> {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
@@ -247,10 +252,10 @@ impl KeyBackups {
|
||||
prefix.extend_from_slice(room_id.as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
self.backupkeyid_backup
|
||||
.scan_prefix(&prefix)
|
||||
.map(|r| {
|
||||
let (key, value) = r?;
|
||||
Ok(self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(prefix)
|
||||
.map(|(key, value)| {
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let session_id =
|
||||
@@ -268,7 +273,7 @@ impl KeyBackups {
|
||||
Ok::<_, Error>((session_id, key_data))
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn get_session(
|
||||
@@ -302,13 +307,8 @@ impl KeyBackups {
|
||||
key.extend_from_slice(&version.as_bytes());
|
||||
key.push(0xff);
|
||||
|
||||
for outdated_key in self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(&key)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
self.backupkeyid_backup.remove(outdated_key)?;
|
||||
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
|
||||
self.backupkeyid_backup.remove(&outdated_key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -327,13 +327,8 @@ impl KeyBackups {
|
||||
key.extend_from_slice(&room_id.as_bytes());
|
||||
key.push(0xff);
|
||||
|
||||
for outdated_key in self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(&key)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
self.backupkeyid_backup.remove(outdated_key)?;
|
||||
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
|
||||
self.backupkeyid_backup.remove(&outdated_key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -354,13 +349,8 @@ impl KeyBackups {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&session_id.as_bytes());
|
||||
|
||||
for outdated_key in self
|
||||
.backupkeyid_backup
|
||||
.scan_prefix(&key)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
{
|
||||
self.backupkeyid_backup.remove(outdated_key)?;
|
||||
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
|
||||
self.backupkeyid_backup.remove(&outdated_key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+95
-48
@@ -1,25 +1,28 @@
|
||||
use crate::database::globals::Globals;
|
||||
use image::{imageops::FilterType, GenericImageView};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
use crate::{utils, Error, Result};
|
||||
use std::mem;
|
||||
use std::{mem, sync::Arc};
|
||||
use tokio::{fs::File, io::AsyncReadExt, io::AsyncWriteExt};
|
||||
|
||||
pub struct FileMeta {
|
||||
pub filename: Option<String>,
|
||||
pub content_disposition: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub file: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Media {
|
||||
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + WidthHeight + Filename + ContentType
|
||||
pub(super) mediaid_file: Arc<dyn Tree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
|
||||
}
|
||||
|
||||
impl Media {
|
||||
/// Uploads or replaces a file.
|
||||
pub fn create(
|
||||
/// Uploads a file.
|
||||
pub async fn create(
|
||||
&self,
|
||||
mxc: String,
|
||||
filename: &Option<&str>,
|
||||
globals: &Globals,
|
||||
content_disposition: &Option<&str>,
|
||||
content_type: &Option<&str>,
|
||||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
@@ -28,7 +31,12 @@ impl Media {
|
||||
key.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
|
||||
key.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
|
||||
key.extend_from_slice(
|
||||
content_disposition
|
||||
.as_ref()
|
||||
.map(|f| f.as_bytes())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(
|
||||
content_type
|
||||
@@ -37,16 +45,20 @@ impl Media {
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
self.mediaid_file.insert(key, file)?;
|
||||
let path = globals.get_media_file(&key);
|
||||
let mut f = File::create(path).await?;
|
||||
f.write_all(file).await?;
|
||||
|
||||
self.mediaid_file.insert(&key, &[])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uploads or replaces a file thumbnail.
|
||||
pub fn upload_thumbnail(
|
||||
pub async fn upload_thumbnail(
|
||||
&self,
|
||||
mxc: String,
|
||||
filename: &Option<String>,
|
||||
globals: &Globals,
|
||||
content_disposition: &Option<String>,
|
||||
content_type: &Option<String>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -57,7 +69,12 @@ impl Media {
|
||||
key.extend_from_slice(&width.to_be_bytes());
|
||||
key.extend_from_slice(&height.to_be_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
|
||||
key.extend_from_slice(
|
||||
content_disposition
|
||||
.as_ref()
|
||||
.map(|f| f.as_bytes())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(
|
||||
content_type
|
||||
@@ -66,48 +83,59 @@ impl Media {
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
self.mediaid_file.insert(key, file)?;
|
||||
let path = globals.get_media_file(&key);
|
||||
let mut f = File::create(path).await?;
|
||||
f.write_all(file).await?;
|
||||
|
||||
self.mediaid_file.insert(&key, &[])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Downloads a file.
|
||||
pub fn get(&self, mxc: &str) -> Result<Option<FileMeta>> {
|
||||
pub async fn get(&self, globals: &Globals, mxc: &str) -> Result<Option<FileMeta>> {
|
||||
let mut prefix = mxc.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
|
||||
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
prefix.push(0xff);
|
||||
|
||||
if let Some(r) = self.mediaid_file.scan_prefix(&prefix).next() {
|
||||
let (key, file) = r?;
|
||||
let mut iter = self.mediaid_file.scan_prefix(prefix);
|
||||
if let Some((key, _)) = iter.next() {
|
||||
let path = globals.get_media_file(&key);
|
||||
let mut file = Vec::new();
|
||||
File::open(path).await?.read_to_end(&mut file).await?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
|
||||
utils::string_from_bytes(bytes).map_err(|_| {
|
||||
Error::bad_database("Content type in mediaid_file is invalid unicode.")
|
||||
})?)
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let filename_bytes = parts
|
||||
let content_disposition_bytes = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
||||
|
||||
let filename = if filename_bytes.is_empty() {
|
||||
let content_disposition = if content_disposition_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(utils::string_from_bytes(filename_bytes).map_err(|_| {
|
||||
Error::bad_database("Filename in mediaid_file is invalid unicode.")
|
||||
})?)
|
||||
Some(
|
||||
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Content Disposition in mediaid_file is invalid unicode.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file: file.to_vec(),
|
||||
file,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -137,7 +165,13 @@ impl Media {
|
||||
/// - Server creates the thumbnail and sends it to the user
|
||||
///
|
||||
/// For width,height <= 96 the server uses another thumbnailing algorithm which crops the image afterwards.
|
||||
pub fn get_thumbnail(&self, mxc: String, width: u32, height: u32) -> Result<Option<FileMeta>> {
|
||||
pub async fn get_thumbnail(
|
||||
&self,
|
||||
mxc: String,
|
||||
globals: &Globals,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Option<FileMeta>> {
|
||||
let (width, height, crop) = self
|
||||
.thumbnail_properties(width, height)
|
||||
.unwrap_or((0, 0, false)); // 0, 0 because that's the original file
|
||||
@@ -155,63 +189,72 @@ impl Media {
|
||||
original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
original_prefix.push(0xff);
|
||||
|
||||
if let Some(r) = self.mediaid_file.scan_prefix(&thumbnail_prefix).next() {
|
||||
if let Some((key, _)) = self.mediaid_file.scan_prefix(thumbnail_prefix).next() {
|
||||
// Using saved thumbnail
|
||||
let (key, file) = r?;
|
||||
let path = globals.get_media_file(&key);
|
||||
let mut file = Vec::new();
|
||||
File::open(path).await?.read_to_end(&mut file).await?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
|
||||
utils::string_from_bytes(bytes).map_err(|_| {
|
||||
Error::bad_database("Content type in mediaid_file is invalid unicode.")
|
||||
})?)
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let filename_bytes = parts
|
||||
let content_disposition_bytes = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
||||
|
||||
let filename = if filename_bytes.is_empty() {
|
||||
let content_disposition = if content_disposition_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
utils::string_from_bytes(filename_bytes)
|
||||
.map_err(|_| Error::bad_database("Filename in db is invalid."))?,
|
||||
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
|
||||
Error::bad_database("Content Disposition in db is invalid.")
|
||||
})?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file: file.to_vec(),
|
||||
}))
|
||||
} else if let Some(r) = self.mediaid_file.scan_prefix(&original_prefix).next() {
|
||||
} else if let Some((key, _)) = self.mediaid_file.scan_prefix(original_prefix).next() {
|
||||
// Generate a thumbnail
|
||||
let path = globals.get_media_file(&key);
|
||||
let mut file = Vec::new();
|
||||
File::open(path).await?.read_to_end(&mut file).await?;
|
||||
|
||||
let (key, file) = r?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
|
||||
utils::string_from_bytes(bytes).map_err(|_| {
|
||||
Error::bad_database("Content type in mediaid_file is invalid unicode.")
|
||||
})?)
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let filename_bytes = parts
|
||||
let content_disposition_bytes = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
||||
|
||||
let filename = if filename_bytes.is_empty() {
|
||||
let content_disposition = if content_disposition_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(utils::string_from_bytes(filename_bytes).map_err(|_| {
|
||||
Error::bad_database("Filename in mediaid_file is invalid unicode.")
|
||||
})?)
|
||||
Some(
|
||||
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Content Disposition in mediaid_file is invalid unicode.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
};
|
||||
|
||||
if let Ok(image) = image::load_from_memory(&file) {
|
||||
@@ -219,7 +262,7 @@ impl Media {
|
||||
let original_height = image.height();
|
||||
if width > original_width || height > original_height {
|
||||
return Ok(Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file: file.to_vec(),
|
||||
}));
|
||||
@@ -283,17 +326,21 @@ impl Media {
|
||||
widthheight,
|
||||
);
|
||||
|
||||
self.mediaid_file.insert(thumbnail_key, &*thumbnail_bytes)?;
|
||||
let path = globals.get_media_file(&thumbnail_key);
|
||||
let mut f = File::create(path).await?;
|
||||
f.write_all(&thumbnail_bytes).await?;
|
||||
|
||||
self.mediaid_file.insert(&thumbnail_key, &[])?;
|
||||
|
||||
Ok(Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file: thumbnail_bytes.to_vec(),
|
||||
}))
|
||||
} else {
|
||||
// Couldn't parse file to generate thumbnail, send original
|
||||
Ok(Some(FileMeta {
|
||||
filename,
|
||||
content_disposition,
|
||||
content_type,
|
||||
file: file.to_vec(),
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
use reqwest::{Proxy, Url};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// ## Examples:
|
||||
/// - No proxy (default):
|
||||
/// ```toml
|
||||
/// proxy ="none"
|
||||
/// ```
|
||||
/// - Global proxy
|
||||
/// ```toml
|
||||
/// [proxy]
|
||||
/// global = { url = "socks5h://localhost:9050" }
|
||||
/// ```
|
||||
/// - Proxy some domains
|
||||
/// ```toml
|
||||
/// [proxy]
|
||||
/// [[proxy.by_domain]]
|
||||
/// url = "socks5h://localhost:9050"
|
||||
/// include = ["*.onion", "matrix.myspecial.onion"]
|
||||
/// exclude = ["*.myspecial.onion"]
|
||||
/// ```
|
||||
/// ## Include vs. Exclude
|
||||
/// If include is an empty list, it is assumed to be `["*"]`.
|
||||
///
|
||||
/// If a domain matches both the exclude and include list, the proxy will only be used if it was
|
||||
/// included because of a more specific rule than it was excluded. In the above example, the proxy
|
||||
/// would be used for `ordinary.onion`, `matrix.myspecial.onion`, but not `hello.myspecial.onion`.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProxyConfig {
|
||||
None,
|
||||
Global {
|
||||
#[serde(deserialize_with = "crate::utils::deserialize_from_str")]
|
||||
url: Url,
|
||||
},
|
||||
ByDomain(Vec<PartialProxyConfig>),
|
||||
}
|
||||
impl ProxyConfig {
|
||||
pub fn to_proxy(&self) -> Result<Option<Proxy>> {
|
||||
Ok(match self.clone() {
|
||||
ProxyConfig::None => None,
|
||||
ProxyConfig::Global { url } => Some(Proxy::all(url)?),
|
||||
ProxyConfig::ByDomain(proxies) => Some(Proxy::custom(move |url| {
|
||||
proxies.iter().find_map(|proxy| proxy.for_url(url)).cloned() // first matching proxy
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Default for ProxyConfig {
|
||||
fn default() -> Self {
|
||||
ProxyConfig::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct PartialProxyConfig {
|
||||
#[serde(deserialize_with = "crate::utils::deserialize_from_str")]
|
||||
url: Url,
|
||||
#[serde(default)]
|
||||
include: Vec<WildCardedDomain>,
|
||||
#[serde(default)]
|
||||
exclude: Vec<WildCardedDomain>,
|
||||
}
|
||||
impl PartialProxyConfig {
|
||||
pub fn for_url(&self, url: &Url) -> Option<&Url> {
|
||||
let domain = url.domain()?;
|
||||
let mut included_because = None; // most specific reason it was included
|
||||
let mut excluded_because = None; // most specific reason it was excluded
|
||||
if self.include.is_empty() {
|
||||
// treat empty include list as `*`
|
||||
included_because = Some(&WildCardedDomain::WildCard)
|
||||
}
|
||||
for wc_domain in &self.include {
|
||||
if wc_domain.matches(domain) {
|
||||
match included_because {
|
||||
Some(prev) if !wc_domain.more_specific_than(prev) => (),
|
||||
_ => included_because = Some(wc_domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
for wc_domain in &self.exclude {
|
||||
if wc_domain.matches(domain) {
|
||||
match excluded_because {
|
||||
Some(prev) if !wc_domain.more_specific_than(prev) => (),
|
||||
_ => excluded_because = Some(wc_domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
match (included_because, excluded_because) {
|
||||
(Some(a), Some(b)) if a.more_specific_than(b) => Some(&self.url), // included for a more specific reason than excluded
|
||||
(Some(_), None) => Some(&self.url),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A domain name, that optionally allows a * as its first subdomain.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WildCardedDomain {
|
||||
WildCard,
|
||||
WildCarded(String),
|
||||
Exact(String),
|
||||
}
|
||||
impl WildCardedDomain {
|
||||
pub fn matches(&self, domain: &str) -> bool {
|
||||
match self {
|
||||
WildCardedDomain::WildCard => true,
|
||||
WildCardedDomain::WildCarded(d) => domain.ends_with(d),
|
||||
WildCardedDomain::Exact(d) => domain == d,
|
||||
}
|
||||
}
|
||||
pub fn more_specific_than(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(WildCardedDomain::WildCard, WildCardedDomain::WildCard) => false,
|
||||
(_, WildCardedDomain::WildCard) => true,
|
||||
(WildCardedDomain::Exact(a), WildCardedDomain::WildCarded(_)) => other.matches(a),
|
||||
(WildCardedDomain::WildCarded(a), WildCardedDomain::WildCarded(b)) => {
|
||||
a != b && a.ends_with(b)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for WildCardedDomain {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
// maybe do some domain validation?
|
||||
Ok(if s.starts_with("*.") {
|
||||
WildCardedDomain::WildCarded(s[1..].to_owned())
|
||||
} else if s == "*" {
|
||||
WildCardedDomain::WildCarded("".to_owned())
|
||||
} else {
|
||||
WildCardedDomain::Exact(s.to_owned())
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<'de> serde::de::Deserialize<'de> for WildCardedDomain {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
crate::utils::deserialize_from_str(deserializer)
|
||||
}
|
||||
}
|
||||
+36
-44
@@ -14,23 +14,17 @@ use ruma::{
|
||||
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
||||
uint, UInt, UserId,
|
||||
};
|
||||
use sled::IVec;
|
||||
|
||||
use std::{convert::TryFrom, fmt::Debug};
|
||||
use std::{convert::TryFrom, fmt::Debug, mem, sync::Arc};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PushData {
|
||||
/// UserId + pushkey -> Pusher
|
||||
pub(super) senderkey_pusher: sled::Tree,
|
||||
pub(super) senderkey_pusher: Arc<dyn Tree>,
|
||||
}
|
||||
|
||||
impl PushData {
|
||||
pub fn new(db: &sled::Db) -> Result<Self> {
|
||||
Ok(Self {
|
||||
senderkey_pusher: db.open_tree("senderkey_pusher")?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_pusher(&self, sender: &UserId, pusher: set_pusher::Pusher) -> Result<()> {
|
||||
let mut key = sender.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
@@ -40,14 +34,14 @@ impl PushData {
|
||||
if pusher.kind.is_none() {
|
||||
return self
|
||||
.senderkey_pusher
|
||||
.remove(key)
|
||||
.remove(&key)
|
||||
.map(|_| ())
|
||||
.map_err(Into::into);
|
||||
}
|
||||
|
||||
self.senderkey_pusher.insert(
|
||||
key,
|
||||
&*serde_json::to_string(&pusher).expect("Pusher is valid JSON string"),
|
||||
&key,
|
||||
&serde_json::to_vec(&pusher).expect("Pusher is valid JSON value"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -57,8 +51,8 @@ impl PushData {
|
||||
self.senderkey_pusher
|
||||
.get(senderkey)?
|
||||
.map(|push| {
|
||||
Ok(serde_json::from_slice(&*push)
|
||||
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
|
||||
serde_json::from_slice(&*push)
|
||||
.map_err(|_| Error::bad_database("Invalid Pusher in db."))
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
@@ -69,23 +63,21 @@ impl PushData {
|
||||
|
||||
self.senderkey_pusher
|
||||
.scan_prefix(prefix)
|
||||
.values()
|
||||
.map(|push| {
|
||||
let push = push.map_err(|_| Error::bad_database("Invalid push bytes in db."))?;
|
||||
Ok(serde_json::from_slice(&*push)
|
||||
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
|
||||
.map(|(_, push)| {
|
||||
serde_json::from_slice(&*push)
|
||||
.map_err(|_| Error::bad_database("Invalid Pusher in db."))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_pusher_senderkeys(&self, sender: &UserId) -> impl Iterator<Item = Result<IVec>> {
|
||||
pub fn get_pusher_senderkeys<'a>(
|
||||
&'a self,
|
||||
sender: &UserId,
|
||||
) -> impl Iterator<Item = Vec<u8>> + 'a {
|
||||
let mut prefix = sender.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
self.senderkey_pusher
|
||||
.scan_prefix(prefix)
|
||||
.keys()
|
||||
.map(|r| Ok(r?))
|
||||
self.senderkey_pusher.scan_prefix(prefix).map(|(k, _)| k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,24 +106,23 @@ where
|
||||
//*reqwest_request.timeout_mut() = Some(Duration::from_secs(5));
|
||||
|
||||
let url = reqwest_request.url().clone();
|
||||
let reqwest_response = globals.reqwest_client().execute(reqwest_request).await;
|
||||
let response = globals.reqwest_client().execute(reqwest_request).await;
|
||||
|
||||
// Because reqwest::Response -> http::Response is complicated:
|
||||
match reqwest_response {
|
||||
Ok(mut reqwest_response) => {
|
||||
let status = reqwest_response.status();
|
||||
let mut http_response = http::Response::builder().status(status);
|
||||
let headers = http_response.headers_mut().unwrap();
|
||||
match response {
|
||||
Ok(mut response) => {
|
||||
// reqwest::Response -> http::Response conversion
|
||||
let status = response.status();
|
||||
let mut http_response_builder = http::Response::builder()
|
||||
.status(status)
|
||||
.version(response.version());
|
||||
mem::swap(
|
||||
response.headers_mut(),
|
||||
http_response_builder
|
||||
.headers_mut()
|
||||
.expect("http::response::Builder is usable"),
|
||||
);
|
||||
|
||||
for (k, v) in reqwest_response.headers_mut().drain() {
|
||||
if let Some(key) = k {
|
||||
headers.insert(key, v);
|
||||
}
|
||||
}
|
||||
|
||||
let status = reqwest_response.status();
|
||||
|
||||
let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
|
||||
let body = response.bytes().await.unwrap_or_else(|e| {
|
||||
warn!("server error {}", e);
|
||||
Vec::new().into()
|
||||
}); // TODO: handle timeout
|
||||
@@ -147,7 +138,7 @@ where
|
||||
}
|
||||
|
||||
let response = T::IncomingResponse::try_from_http_response(
|
||||
http_response
|
||||
http_response_builder
|
||||
.body(body)
|
||||
.expect("reqwest body is valid http body"),
|
||||
);
|
||||
@@ -212,7 +203,7 @@ pub fn get_actions<'a>(
|
||||
.rooms
|
||||
.room_state_get(&pdu.room_id, &EventType::RoomPowerLevels, "")?
|
||||
.map(|ev| {
|
||||
serde_json::from_value(ev.content)
|
||||
serde_json::from_value(ev.content.clone())
|
||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
||||
})
|
||||
.transpose()?
|
||||
@@ -295,7 +286,8 @@ async fn send_notice(
|
||||
} else {
|
||||
notifi.sender = Some(&event.sender);
|
||||
notifi.event_type = Some(&event.kind);
|
||||
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
|
||||
let content = serde_json::value::to_raw_value(&event.content).ok();
|
||||
notifi.content = content.as_deref();
|
||||
|
||||
if event.kind == EventType::RoomMember {
|
||||
notifi.user_is_target = event.state_key.as_deref() == Some(event.sender.as_str());
|
||||
|
||||
+390
-346
File diff suppressed because it is too large
Load Diff
+120
-64
@@ -1,28 +1,29 @@
|
||||
use crate::{utils, Error, Result};
|
||||
use crate::{database::abstraction::Tree, utils, Error, Result};
|
||||
use ruma::{
|
||||
events::{
|
||||
presence::{PresenceEvent, PresenceEventContent},
|
||||
AnyEvent as EduEvent, SyncEphemeralRoomEvent,
|
||||
AnyEphemeralRoomEvent, SyncEphemeralRoomEvent,
|
||||
},
|
||||
presence::PresenceState,
|
||||
serde::Raw,
|
||||
signatures::CanonicalJsonObject,
|
||||
RoomId, UInt, UserId,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
mem,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RoomEdus {
|
||||
pub(in super::super) readreceiptid_readreceipt: sled::Tree, // ReadReceiptId = RoomId + Count + UserId
|
||||
pub(in super::super) roomuserid_privateread: sled::Tree, // RoomUserId = Room + User, PrivateRead = Count
|
||||
pub(in super::super) roomuserid_lastprivatereadupdate: sled::Tree, // LastPrivateReadUpdate = Count
|
||||
pub(in super::super) typingid_userid: sled::Tree, // TypingId = RoomId + TimeoutTime + Count
|
||||
pub(in super::super) roomid_lasttypingupdate: sled::Tree, // LastRoomTypingUpdate = Count
|
||||
pub(in super::super) presenceid_presence: sled::Tree, // PresenceId = RoomId + Count + UserId
|
||||
pub(in super::super) userid_lastpresenceupdate: sled::Tree, // LastPresenceUpdate = Count
|
||||
pub(in super::super) readreceiptid_readreceipt: Arc<dyn Tree>, // ReadReceiptId = RoomId + Count + UserId
|
||||
pub(in super::super) roomuserid_privateread: Arc<dyn Tree>, // RoomUserId = Room + User, PrivateRead = Count
|
||||
pub(in super::super) roomuserid_lastprivatereadupdate: Arc<dyn Tree>, // LastPrivateReadUpdate = Count
|
||||
pub(in super::super) typingid_userid: Arc<dyn Tree>, // TypingId = RoomId + TimeoutTime + Count
|
||||
pub(in super::super) roomid_lasttypingupdate: Arc<dyn Tree>, // LastRoomTypingUpdate = Count
|
||||
pub(in super::super) presenceid_presence: Arc<dyn Tree>, // PresenceId = RoomId + Count + UserId
|
||||
pub(in super::super) userid_lastpresenceupdate: Arc<dyn Tree>, // LastPresenceUpdate = Count
|
||||
}
|
||||
|
||||
impl RoomEdus {
|
||||
@@ -31,21 +32,21 @@ impl RoomEdus {
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
event: EduEvent,
|
||||
event: AnyEphemeralRoomEvent,
|
||||
globals: &super::super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut last_possible_key = prefix.clone();
|
||||
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
|
||||
|
||||
// Remove old entry
|
||||
if let Some(old) = self
|
||||
if let Some((old, _)) = self
|
||||
.readreceiptid_readreceipt
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.rev()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|key| key.starts_with(&prefix))
|
||||
.find(|key| {
|
||||
.iter_from(&last_possible_key, true)
|
||||
.take_while(|(key, _)| key.starts_with(&prefix))
|
||||
.find(|(key, _)| {
|
||||
key.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.expect("rsplit always returns an element")
|
||||
@@ -53,7 +54,7 @@ impl RoomEdus {
|
||||
})
|
||||
{
|
||||
// This is the old room_latest
|
||||
self.readreceiptid_readreceipt.remove(old)?;
|
||||
self.readreceiptid_readreceipt.remove(&old)?;
|
||||
}
|
||||
|
||||
let mut room_latest_id = prefix;
|
||||
@@ -62,8 +63,8 @@ impl RoomEdus {
|
||||
room_latest_id.extend_from_slice(&user_id.as_bytes());
|
||||
|
||||
self.readreceiptid_readreceipt.insert(
|
||||
room_latest_id,
|
||||
&*serde_json::to_string(&event).expect("EduEvent::to_string always works"),
|
||||
&room_latest_id,
|
||||
&serde_json::to_vec(&event).expect("EduEvent::to_string always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -71,27 +72,47 @@ impl RoomEdus {
|
||||
|
||||
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn readreceipts_since(
|
||||
&self,
|
||||
pub fn readreceipts_since<'a>(
|
||||
&'a self,
|
||||
room_id: &RoomId,
|
||||
since: u64,
|
||||
) -> Result<impl Iterator<Item = Result<Raw<ruma::events::AnySyncEphemeralRoomEvent>>>> {
|
||||
) -> impl Iterator<Item = Result<(UserId, u64, Raw<ruma::events::AnySyncEphemeralRoomEvent>)>> + 'a
|
||||
{
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
let prefix2 = prefix.clone();
|
||||
|
||||
let mut first_possible_edu = prefix.clone();
|
||||
first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since
|
||||
|
||||
Ok(self
|
||||
.readreceiptid_readreceipt
|
||||
.range(&*first_possible_edu..)
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| {
|
||||
Ok(serde_json::from_slice(&v).map_err(|_| {
|
||||
Error::bad_database("Read receipt in roomlatestid_roomlatest is invalid.")
|
||||
})?)
|
||||
}))
|
||||
self.readreceiptid_readreceipt
|
||||
.iter_from(&first_possible_edu, false)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix2))
|
||||
.map(move |(k, v)| {
|
||||
let count =
|
||||
utils::u64_from_bytes(&k[prefix.len()..prefix.len() + mem::size_of::<u64>()])
|
||||
.map_err(|_| Error::bad_database("Invalid readreceiptid count in db."))?;
|
||||
let user_id = UserId::try_from(
|
||||
utils::string_from_bytes(&k[prefix.len() + mem::size_of::<u64>() + 1..])
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid readreceiptid userid bytes in db.")
|
||||
})?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("Invalid readreceiptid userid in db."))?;
|
||||
|
||||
let mut json = serde_json::from_slice::<CanonicalJsonObject>(&v).map_err(|_| {
|
||||
Error::bad_database("Read receipt in roomlatestid_roomlatest is invalid json.")
|
||||
})?;
|
||||
json.remove("room_id");
|
||||
|
||||
Ok((
|
||||
user_id,
|
||||
count,
|
||||
Raw::from_json(
|
||||
serde_json::value::to_raw_value(&json).expect("json is valid raw value"),
|
||||
),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets a private read marker at `count`.
|
||||
@@ -122,11 +143,13 @@ impl RoomEdus {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&user_id.as_bytes());
|
||||
|
||||
self.roomuserid_privateread.get(key)?.map_or(Ok(None), |v| {
|
||||
Ok(Some(utils::u64_from_bytes(&v).map_err(|_| {
|
||||
Error::bad_database("Invalid private read marker bytes")
|
||||
})?))
|
||||
})
|
||||
self.roomuserid_privateread
|
||||
.get(&key)?
|
||||
.map_or(Ok(None), |v| {
|
||||
Ok(Some(utils::u64_from_bytes(&v).map_err(|_| {
|
||||
Error::bad_database("Invalid private read marker bytes")
|
||||
})?))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the count of the last typing update in this room.
|
||||
@@ -191,11 +214,10 @@ impl RoomEdus {
|
||||
// Maybe there are multiple ones from calling roomtyping_add multiple times
|
||||
for outdated_edu in self
|
||||
.typingid_userid
|
||||
.scan_prefix(&prefix)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_, v)| v == user_id.as_bytes())
|
||||
.scan_prefix(prefix)
|
||||
.filter(|(_, v)| &**v == user_id.as_bytes())
|
||||
{
|
||||
self.typingid_userid.remove(outdated_edu.0)?;
|
||||
self.typingid_userid.remove(&outdated_edu.0)?;
|
||||
found_outdated = true;
|
||||
}
|
||||
|
||||
@@ -223,10 +245,8 @@ impl RoomEdus {
|
||||
// Find all outdated edus before inserting a new one
|
||||
for outdated_edu in self
|
||||
.typingid_userid
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.map(|key| {
|
||||
let key = key?;
|
||||
.scan_prefix(prefix)
|
||||
.map(|(key, _)| {
|
||||
Ok::<_, Error>((
|
||||
key.clone(),
|
||||
utils::u64_from_bytes(
|
||||
@@ -241,7 +261,7 @@ impl RoomEdus {
|
||||
.take_while(|&(_, timestamp)| timestamp < current_timestamp)
|
||||
{
|
||||
// This is an outdated edu (time > timestamp)
|
||||
self.typingid_userid.remove(outdated_edu.0)?;
|
||||
self.typingid_userid.remove(&outdated_edu.0)?;
|
||||
found_outdated = true;
|
||||
}
|
||||
|
||||
@@ -285,14 +305,11 @@ impl RoomEdus {
|
||||
for user_id in self
|
||||
.typingid_userid
|
||||
.scan_prefix(prefix)
|
||||
.values()
|
||||
.map(|user_id| {
|
||||
Ok::<_, Error>(
|
||||
UserId::try_from(utils::string_from_bytes(&user_id?).map_err(|_| {
|
||||
Error::bad_database("User ID in typingid_userid is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))?,
|
||||
)
|
||||
.map(|(_, user_id)| {
|
||||
UserId::try_from(utils::string_from_bytes(&user_id).map_err(|_| {
|
||||
Error::bad_database("User ID in typingid_userid is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))
|
||||
})
|
||||
{
|
||||
user_ids.insert(user_id?);
|
||||
@@ -327,12 +344,12 @@ impl RoomEdus {
|
||||
presence_id.extend_from_slice(&presence.sender.as_bytes());
|
||||
|
||||
self.presenceid_presence.insert(
|
||||
presence_id,
|
||||
&*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"),
|
||||
&presence_id,
|
||||
&serde_json::to_vec(&presence).expect("PresenceEvent can be serialized"),
|
||||
)?;
|
||||
|
||||
self.userid_lastpresenceupdate.insert(
|
||||
&user_id.as_bytes(),
|
||||
user_id.as_bytes(),
|
||||
&utils::millis_since_unix_epoch().to_be_bytes(),
|
||||
)?;
|
||||
|
||||
@@ -362,6 +379,47 @@ impl RoomEdus {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn get_last_presence_event(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<PresenceEvent>> {
|
||||
let last_update = match self.last_presence_update(user_id)? {
|
||||
Some(last) => last,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let mut presence_id = room_id.as_bytes().to_vec();
|
||||
presence_id.push(0xff);
|
||||
presence_id.extend_from_slice(&last_update.to_be_bytes());
|
||||
presence_id.push(0xff);
|
||||
presence_id.extend_from_slice(&user_id.as_bytes());
|
||||
|
||||
self.presenceid_presence
|
||||
.get(&presence_id)?
|
||||
.map(|value| {
|
||||
let mut presence = serde_json::from_slice::<PresenceEvent>(&value)
|
||||
.map_err(|_| Error::bad_database("Invalid presence event in db."))?;
|
||||
let current_timestamp: UInt = utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("time is valid");
|
||||
|
||||
if presence.content.presence == PresenceState::Online {
|
||||
// Don't set last_active_ago when the user is online
|
||||
presence.content.last_active_ago = None;
|
||||
} else {
|
||||
// Convert from timestamp to duration
|
||||
presence.content.last_active_ago = presence
|
||||
.content
|
||||
.last_active_ago
|
||||
.map(|timestamp| current_timestamp - timestamp);
|
||||
}
|
||||
|
||||
Ok(presence)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Sets all users to offline who have been quiet for too long.
|
||||
pub fn presence_maintain(
|
||||
&self,
|
||||
@@ -373,7 +431,6 @@ impl RoomEdus {
|
||||
for (user_id_bytes, last_timestamp) in self
|
||||
.userid_lastpresenceupdate
|
||||
.iter()
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|(k, bytes)| {
|
||||
Some((
|
||||
k,
|
||||
@@ -403,8 +460,8 @@ impl RoomEdus {
|
||||
presence_id.extend_from_slice(&user_id_bytes);
|
||||
|
||||
self.presenceid_presence.insert(
|
||||
presence_id,
|
||||
&*serde_json::to_string(&PresenceEvent {
|
||||
&presence_id,
|
||||
&serde_json::to_vec(&PresenceEvent {
|
||||
content: PresenceEventContent {
|
||||
avatar_url: None,
|
||||
currently_active: None,
|
||||
@@ -450,8 +507,7 @@ impl RoomEdus {
|
||||
|
||||
for (key, value) in self
|
||||
.presenceid_presence
|
||||
.range(&*first_possible_edu..)
|
||||
.filter_map(|r| r.ok())
|
||||
.iter_from(&*first_possible_edu, false)
|
||||
.take_while(|(key, _)| key.starts_with(&prefix))
|
||||
{
|
||||
let user_id = UserId::try_from(
|
||||
|
||||
+394
-274
@@ -1,9 +1,9 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{BTreeMap, HashMap},
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -12,14 +12,30 @@ use crate::{
|
||||
use federation::transactions::send_transaction_message;
|
||||
use log::{error, warn};
|
||||
use ring::digest;
|
||||
use rocket::futures::stream::{FuturesUnordered, StreamExt};
|
||||
use ruma::{
|
||||
api::{appservice, federation, OutgoingRequest},
|
||||
events::{push_rules, EventType},
|
||||
push, ServerName, UInt, UserId,
|
||||
use rocket::futures::{
|
||||
channel::mpsc,
|
||||
stream::{FuturesUnordered, StreamExt},
|
||||
};
|
||||
use sled::IVec;
|
||||
use tokio::{select, sync::Semaphore};
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice,
|
||||
federation::{
|
||||
self,
|
||||
transactions::edu::{Edu, ReceiptContent, ReceiptData, ReceiptMap},
|
||||
},
|
||||
OutgoingRequest,
|
||||
},
|
||||
events::{push_rules, AnySyncEphemeralRoomEvent, EventType},
|
||||
push,
|
||||
receipt::ReceiptType,
|
||||
MilliSecondsSinceUnixEpoch, ServerName, UInt, UserId,
|
||||
};
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{RwLock, Semaphore},
|
||||
};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum OutgoingKind {
|
||||
@@ -28,12 +44,46 @@ pub enum OutgoingKind {
|
||||
Normal(Box<ServerName>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
impl OutgoingKind {
|
||||
pub fn get_prefix(&self) -> Vec<u8> {
|
||||
let mut prefix = match self {
|
||||
OutgoingKind::Appservice(server) => {
|
||||
let mut p = b"+".to_vec();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&user);
|
||||
p.push(0xff);
|
||||
p.extend_from_slice(&pushkey);
|
||||
p
|
||||
}
|
||||
OutgoingKind::Normal(server) => {
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
};
|
||||
prefix.push(0xff);
|
||||
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum SendingEventType {
|
||||
Pdu(Vec<u8>),
|
||||
Edu(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct Sending {
|
||||
/// The state for a given state hash.
|
||||
pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
|
||||
pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+ / $)ServerName / UserId + PduId
|
||||
pub(super) servername_educount: Arc<dyn Tree>, // EduCount: Count of last EDU sync
|
||||
pub(super) servernamepduids: Arc<dyn Tree>, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
|
||||
pub(super) servercurrentevents: Arc<dyn Tree>, // ServerCurrentEvents = (+ / $)ServerName / UserId + PduId / (*)EduEvent
|
||||
pub(super) maximum_requests: Arc<Semaphore>,
|
||||
pub sender: mpsc::UnboundedSender<Vec<u8>>,
|
||||
}
|
||||
|
||||
enum TransactionStatus {
|
||||
@@ -43,28 +93,31 @@ enum TransactionStatus {
|
||||
}
|
||||
|
||||
impl Sending {
|
||||
pub fn start_handler(&self, db: &Database) {
|
||||
let servernamepduids = self.servernamepduids.clone();
|
||||
let servercurrentpdus = self.servercurrentpdus.clone();
|
||||
|
||||
let db = db.clone();
|
||||
|
||||
pub fn start_handler(
|
||||
&self,
|
||||
db: Arc<RwLock<Database>>,
|
||||
mut receiver: mpsc::UnboundedReceiver<Vec<u8>>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
// Retry requests we could not finish yet
|
||||
let mut subscriber = servernamepduids.watch_prefix(b"");
|
||||
let mut current_transaction_status = HashMap::<Vec<u8>, TransactionStatus>::new();
|
||||
|
||||
let mut initial_transactions = HashMap::<OutgoingKind, Vec<Vec<u8>>>::new();
|
||||
for (key, outgoing_kind, pdu) in servercurrentpdus
|
||||
.iter()
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|(key, _)| {
|
||||
Self::parse_servercurrentpdus(&key)
|
||||
.ok()
|
||||
.map(|(k, p)| (key, k, p.to_vec()))
|
||||
})
|
||||
// Retry requests we could not finish yet
|
||||
let mut initial_transactions = HashMap::<OutgoingKind, Vec<SendingEventType>>::new();
|
||||
|
||||
let guard = db.read().await;
|
||||
|
||||
for (key, outgoing_kind, event) in
|
||||
guard
|
||||
.sending
|
||||
.servercurrentevents
|
||||
.iter()
|
||||
.filter_map(|(key, _)| {
|
||||
Self::parse_servercurrentevent(&key)
|
||||
.ok()
|
||||
.map(|(k, e)| (key, k, e))
|
||||
})
|
||||
{
|
||||
let entry = initial_transactions
|
||||
.entry(outgoing_kind.clone())
|
||||
@@ -72,39 +125,26 @@ impl Sending {
|
||||
|
||||
if entry.len() > 30 {
|
||||
warn!(
|
||||
"Dropping some current pdu: {:?} {:?} {:?}",
|
||||
key, outgoing_kind, pdu
|
||||
"Dropping some current events: {:?} {:?} {:?}",
|
||||
key, outgoing_kind, event
|
||||
);
|
||||
servercurrentpdus.remove(key).unwrap();
|
||||
guard.sending.servercurrentevents.remove(&key).unwrap();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.push(pdu);
|
||||
entry.push(event);
|
||||
}
|
||||
|
||||
for (outgoing_kind, pdus) in initial_transactions {
|
||||
let mut prefix = match &outgoing_kind {
|
||||
OutgoingKind::Appservice(server) => {
|
||||
let mut p = b"+".to_vec();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&user);
|
||||
p.push(0xff);
|
||||
p.extend_from_slice(&pushkey);
|
||||
p
|
||||
}
|
||||
OutgoingKind::Normal(server) => {
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
};
|
||||
prefix.push(0xff);
|
||||
current_transaction_status.insert(prefix, TransactionStatus::Running);
|
||||
futures.push(Self::handle_event(outgoing_kind.clone(), pdus, &db));
|
||||
drop(guard);
|
||||
|
||||
for (outgoing_kind, events) in initial_transactions {
|
||||
current_transaction_status
|
||||
.insert(outgoing_kind.get_prefix(), TransactionStatus::Running);
|
||||
futures.push(Self::handle_events(
|
||||
outgoing_kind.clone(),
|
||||
events,
|
||||
Arc::clone(&db),
|
||||
));
|
||||
}
|
||||
|
||||
loop {
|
||||
@@ -112,59 +152,47 @@ impl Sending {
|
||||
Some(response) = futures.next() => {
|
||||
match response {
|
||||
Ok(outgoing_kind) => {
|
||||
let mut prefix = match &outgoing_kind {
|
||||
OutgoingKind::Appservice(server) => {
|
||||
let mut p = b"+".to_vec();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&user);
|
||||
p.push(0xff);
|
||||
p.extend_from_slice(&pushkey);
|
||||
p
|
||||
},
|
||||
OutgoingKind::Normal(server) => {
|
||||
let mut p = vec![];
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
},
|
||||
};
|
||||
prefix.push(0xff);
|
||||
let guard = db.read().await;
|
||||
|
||||
for key in servercurrentpdus
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
let prefix = outgoing_kind.get_prefix();
|
||||
for (key, _) in guard.sending.servercurrentevents
|
||||
.scan_prefix(prefix.clone())
|
||||
{
|
||||
servercurrentpdus.remove(key).unwrap();
|
||||
guard.sending.servercurrentevents.remove(&key).unwrap();
|
||||
}
|
||||
|
||||
// Find events that have been added since starting the last request
|
||||
let new_pdus = servernamepduids
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
.map(|k| {
|
||||
k[prefix.len()..].to_vec()
|
||||
let new_events = guard.sending.servernamepduids
|
||||
.scan_prefix(prefix.clone())
|
||||
.map(|(k, _)| {
|
||||
SendingEventType::Pdu(k[prefix.len()..].to_vec())
|
||||
})
|
||||
.take(30)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !new_pdus.is_empty() {
|
||||
for pdu_id in &new_pdus {
|
||||
// TODO: find edus
|
||||
|
||||
if !new_events.is_empty() {
|
||||
// Insert pdus we found
|
||||
for event in &new_events {
|
||||
let mut current_key = prefix.clone();
|
||||
current_key.extend_from_slice(pdu_id);
|
||||
servercurrentpdus.insert(¤t_key, &[]).unwrap();
|
||||
servernamepduids.remove(¤t_key).unwrap();
|
||||
match event {
|
||||
SendingEventType::Pdu(b) |
|
||||
SendingEventType::Edu(b) => {
|
||||
current_key.extend_from_slice(&b);
|
||||
guard.sending.servercurrentevents.insert(¤t_key, &[]).unwrap();
|
||||
guard.sending.servernamepduids.remove(¤t_key).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(guard);
|
||||
|
||||
futures.push(
|
||||
Self::handle_event(
|
||||
Self::handle_events(
|
||||
outgoing_kind.clone(),
|
||||
new_pdus,
|
||||
&db,
|
||||
new_events,
|
||||
Arc::clone(&db),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
@@ -172,29 +200,7 @@ impl Sending {
|
||||
}
|
||||
}
|
||||
Err((outgoing_kind, _)) => {
|
||||
let mut prefix = match &outgoing_kind {
|
||||
OutgoingKind::Appservice(serv) => {
|
||||
let mut p = b"+".to_vec();
|
||||
p.extend_from_slice(serv.as_bytes());
|
||||
p
|
||||
},
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&user);
|
||||
p.push(0xff);
|
||||
p.extend_from_slice(&pushkey);
|
||||
p
|
||||
},
|
||||
OutgoingKind::Normal(serv) => {
|
||||
let mut p = vec![];
|
||||
p.extend_from_slice(serv.as_bytes());
|
||||
p
|
||||
},
|
||||
};
|
||||
|
||||
prefix.push(0xff);
|
||||
|
||||
current_transaction_status.entry(prefix).and_modify(|e| *e = match e {
|
||||
current_transaction_status.entry(outgoing_kind.get_prefix()).and_modify(|e| *e = match e {
|
||||
TransactionStatus::Running => TransactionStatus::Failed(1, Instant::now()),
|
||||
TransactionStatus::Retrying(n) => TransactionStatus::Failed(*n+1, Instant::now()),
|
||||
TransactionStatus::Failed(_, _) => {
|
||||
@@ -205,98 +211,17 @@ impl Sending {
|
||||
}
|
||||
};
|
||||
},
|
||||
Some(event) = &mut subscriber => {
|
||||
if let sled::Event::Insert { key, .. } = event {
|
||||
// New sled version:
|
||||
//for (_tree, key, value_opt) in &event {
|
||||
// if value_opt.is_none() {
|
||||
// continue;
|
||||
// }
|
||||
Some(key) = receiver.next() => {
|
||||
if let Ok((outgoing_kind, event)) = Self::parse_servercurrentevent(&key) {
|
||||
let guard = db.read().await;
|
||||
|
||||
let servernamepduid = key.clone();
|
||||
|
||||
let mut retry = false;
|
||||
|
||||
if let Some((outgoing_kind, prefix, pdu_id)) = Self::parse_servercurrentpdus(&servernamepduid)
|
||||
.ok()
|
||||
.map(|(outgoing_kind, pdu_id)| {
|
||||
let mut prefix = match &outgoing_kind {
|
||||
OutgoingKind::Appservice(serv) => {
|
||||
let mut p = b"+".to_vec();
|
||||
p.extend_from_slice(serv.as_bytes());
|
||||
p
|
||||
},
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&user);
|
||||
p.push(0xff);
|
||||
p.extend_from_slice(&pushkey);
|
||||
p
|
||||
},
|
||||
OutgoingKind::Normal(serv) => {
|
||||
let mut p = vec![];
|
||||
p.extend_from_slice(serv.as_bytes());
|
||||
p
|
||||
},
|
||||
};
|
||||
prefix.push(0xff);
|
||||
|
||||
(outgoing_kind, prefix, pdu_id)
|
||||
})
|
||||
.filter(|(_, prefix, _)| {
|
||||
let entry = current_transaction_status.entry(prefix.clone());
|
||||
let mut allow = true;
|
||||
|
||||
entry.and_modify(|e| match e {
|
||||
TransactionStatus::Running | TransactionStatus::Retrying(_) => {
|
||||
allow = false; // already running
|
||||
},
|
||||
TransactionStatus::Failed(tries, time) => {
|
||||
// Fail if a request has failed recently (exponential backoff)
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60*60*24) {
|
||||
min_elapsed_duration = Duration::from_secs(60*60*24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
allow = false;
|
||||
} else {
|
||||
retry = true;
|
||||
*e = TransactionStatus::Retrying(*tries);
|
||||
}
|
||||
}
|
||||
}).or_insert(TransactionStatus::Running);
|
||||
|
||||
allow
|
||||
})
|
||||
{
|
||||
let mut pdus = Vec::new();
|
||||
|
||||
if retry {
|
||||
// We retry the previous transaction
|
||||
for pdu in servercurrentpdus
|
||||
.scan_prefix(&prefix)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|(key, _)| {
|
||||
Self::parse_servercurrentpdus(&key)
|
||||
.ok()
|
||||
.map(|(_, p)| p.to_vec())
|
||||
})
|
||||
{
|
||||
pdus.push(pdu);
|
||||
}
|
||||
} else {
|
||||
servercurrentpdus.insert(&key, &[]).unwrap();
|
||||
servernamepduids.remove(&key).unwrap();
|
||||
pdus.push(pdu_id.to_vec());
|
||||
}
|
||||
futures.push(
|
||||
Self::handle_event(
|
||||
outgoing_kind,
|
||||
pdus,
|
||||
&db,
|
||||
)
|
||||
);
|
||||
if let Ok(Some(events)) = Self::select_events(
|
||||
&outgoing_kind,
|
||||
vec![(event, key)],
|
||||
&mut current_transaction_status,
|
||||
&guard
|
||||
) {
|
||||
futures.push(Self::handle_events(outgoing_kind, events, Arc::clone(&db)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,13 +230,163 @@ impl Sending {
|
||||
});
|
||||
}
|
||||
|
||||
fn select_events(
|
||||
outgoing_kind: &OutgoingKind,
|
||||
new_events: Vec<(SendingEventType, Vec<u8>)>, // Events we want to send: event and full key
|
||||
current_transaction_status: &mut HashMap<Vec<u8>, TransactionStatus>,
|
||||
db: &Database,
|
||||
) -> Result<Option<Vec<SendingEventType>>> {
|
||||
let mut retry = false;
|
||||
let mut allow = true;
|
||||
|
||||
let prefix = outgoing_kind.get_prefix();
|
||||
let entry = current_transaction_status.entry(prefix.clone());
|
||||
|
||||
entry
|
||||
.and_modify(|e| match e {
|
||||
TransactionStatus::Running | TransactionStatus::Retrying(_) => {
|
||||
allow = false; // already running
|
||||
}
|
||||
TransactionStatus::Failed(tries, time) => {
|
||||
// Fail if a request has failed recently (exponential backoff)
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
allow = false;
|
||||
} else {
|
||||
retry = true;
|
||||
*e = TransactionStatus::Retrying(*tries);
|
||||
}
|
||||
}
|
||||
})
|
||||
.or_insert(TransactionStatus::Running);
|
||||
|
||||
if !allow {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
if retry {
|
||||
// We retry the previous transaction
|
||||
for (key, _) in db.sending.servercurrentevents.scan_prefix(prefix) {
|
||||
if let Ok((_, e)) = Self::parse_servercurrentevent(&key) {
|
||||
events.push(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (e, full_key) in new_events {
|
||||
db.sending.servercurrentevents.insert(&full_key, &[])?;
|
||||
|
||||
// If it was a PDU we have to unqueue it
|
||||
// TODO: don't try to unqueue EDUs
|
||||
db.sending.servernamepduids.remove(&full_key)?;
|
||||
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
if let OutgoingKind::Normal(server_name) = outgoing_kind {
|
||||
if let Ok((select_edus, last_count)) = Self::select_edus(db, server_name) {
|
||||
events.extend_from_slice(&select_edus);
|
||||
db.sending
|
||||
.servername_educount
|
||||
.insert(server_name.as_bytes(), &last_count.to_be_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(events))
|
||||
}
|
||||
|
||||
pub fn select_edus(db: &Database, server: &ServerName) -> Result<(Vec<SendingEventType>, u64)> {
|
||||
// u64: count of last edu
|
||||
let since = db
|
||||
.sending
|
||||
.servername_educount
|
||||
.get(server.as_bytes())?
|
||||
.map_or(Ok(0), |bytes| {
|
||||
utils::u64_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Invalid u64 in servername_educount."))
|
||||
})?;
|
||||
let mut events = Vec::new();
|
||||
let mut max_edu_count = since;
|
||||
'outer: for room_id in db.rooms.server_rooms(server) {
|
||||
let room_id = room_id?;
|
||||
for r in db.rooms.edus.readreceipts_since(&room_id, since) {
|
||||
let (user_id, count, read_receipt) = r?;
|
||||
|
||||
if count > max_edu_count {
|
||||
max_edu_count = count;
|
||||
}
|
||||
|
||||
if user_id.server_name() != db.globals.server_name() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let event =
|
||||
serde_json::from_str::<AnySyncEphemeralRoomEvent>(&read_receipt.json().get())
|
||||
.map_err(|_| Error::bad_database("Invalid edu event in read_receipts."))?;
|
||||
let federation_event = match event {
|
||||
AnySyncEphemeralRoomEvent::Receipt(r) => {
|
||||
let mut read = BTreeMap::new();
|
||||
|
||||
let (event_id, mut receipt) = r
|
||||
.content
|
||||
.0
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("we only use one event per read receipt");
|
||||
let receipt = receipt
|
||||
.remove(&ReceiptType::Read)
|
||||
.expect("our read receipts always set this")
|
||||
.remove(&user_id)
|
||||
.expect("our read receipts always have the user here");
|
||||
|
||||
read.insert(
|
||||
user_id,
|
||||
ReceiptData {
|
||||
data: receipt.clone(),
|
||||
event_ids: vec![event_id.clone()],
|
||||
},
|
||||
);
|
||||
|
||||
let receipt_map = ReceiptMap { read };
|
||||
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(room_id.clone(), receipt_map);
|
||||
|
||||
Edu::Receipt(ReceiptContent { receipts })
|
||||
}
|
||||
_ => {
|
||||
Error::bad_database("Invalid event type in read_receipts");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
events.push(SendingEventType::Edu(
|
||||
serde_json::to_vec(&federation_event).expect("json can be serialized"),
|
||||
));
|
||||
|
||||
if events.len() >= 20 {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((events, max_edu_count))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: IVec) -> Result<()> {
|
||||
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: Vec<u8>) -> Result<()> {
|
||||
let mut key = b"$".to_vec();
|
||||
key.extend_from_slice(&senderkey);
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(pdu_id);
|
||||
self.servernamepduids.insert(key, b"")?;
|
||||
self.servernamepduids.insert(&key, b"")?;
|
||||
self.sender.unbounded_send(key).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -321,7 +396,8 @@ impl Sending {
|
||||
let mut key = server.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(pdu_id);
|
||||
self.servernamepduids.insert(key, b"")?;
|
||||
self.servernamepduids.insert(&key, b"")?;
|
||||
self.sender.unbounded_send(key).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -332,13 +408,14 @@ impl Sending {
|
||||
key.extend_from_slice(appservice_id.as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(pdu_id);
|
||||
self.servernamepduids.insert(key, b"")?;
|
||||
self.servernamepduids.insert(&key, b"")?;
|
||||
self.sender.unbounded_send(key).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn calculate_hash(keys: &[Vec<u8>]) -> Vec<u8> {
|
||||
fn calculate_hash(keys: &[&[u8]]) -> Vec<u8> {
|
||||
// We only hash the pdu's event ids, not the whole pdu
|
||||
let bytes = keys.join(&0xff);
|
||||
let hash = digest::digest(&digest::SHA256, &bytes);
|
||||
@@ -346,33 +423,39 @@ impl Sending {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(db))]
|
||||
async fn handle_event(
|
||||
async fn handle_events(
|
||||
kind: OutgoingKind,
|
||||
pdu_ids: Vec<Vec<u8>>,
|
||||
db: &Database,
|
||||
events: Vec<SendingEventType>,
|
||||
db: Arc<RwLock<Database>>,
|
||||
) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> {
|
||||
let db = db.read().await;
|
||||
|
||||
match &kind {
|
||||
OutgoingKind::Appservice(server) => {
|
||||
let pdu_jsons = pdu_ids
|
||||
.iter()
|
||||
.map(|pdu_id| {
|
||||
Ok::<_, (Box<ServerName>, Error)>(
|
||||
db.rooms
|
||||
.get_pdu_from_id(pdu_id)
|
||||
.map_err(|e| (server.clone(), e))?
|
||||
let mut pdu_jsons = Vec::new();
|
||||
|
||||
for event in &events {
|
||||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
pdu_jsons.push(db.rooms
|
||||
.get_pdu_from_id(&pdu_id)
|
||||
.map_err(|e| (kind.clone(), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
server.clone(),
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Appservice] Event in servernamepduids not found in db.",
|
||||
),
|
||||
)
|
||||
})?
|
||||
.to_any_event(),
|
||||
)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<_>>();
|
||||
.to_room_event())
|
||||
}
|
||||
SendingEventType::Edu(_) => {
|
||||
// Appservices don't need EDUs (?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let permit = db.sending.maximum_requests.acquire().await;
|
||||
|
||||
let response = appservice_server::send_request(
|
||||
@@ -384,7 +467,14 @@ impl Sending {
|
||||
appservice::event::push_events::v1::Request {
|
||||
events: &pdu_jsons,
|
||||
txn_id: &base64::encode_config(
|
||||
Self::calculate_hash(&pdu_ids),
|
||||
Self::calculate_hash(
|
||||
&events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
),
|
||||
},
|
||||
@@ -398,25 +488,30 @@ impl Sending {
|
||||
response
|
||||
}
|
||||
OutgoingKind::Push(user, pushkey) => {
|
||||
let pdus = pdu_ids
|
||||
.iter()
|
||||
.map(|pdu_id| {
|
||||
Ok::<_, (Vec<u8>, Error)>(
|
||||
db.rooms
|
||||
.get_pdu_from_id(pdu_id)
|
||||
.map_err(|e| (pushkey.clone(), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
pushkey.clone(),
|
||||
Error::bad_database(
|
||||
"[Push] Event in servernamepduids not found in db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<_>>();
|
||||
let mut pdus = Vec::new();
|
||||
|
||||
for event in &events {
|
||||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
pdus.push(
|
||||
db.rooms
|
||||
.get_pdu_from_id(&pdu_id)
|
||||
.map_err(|e| (kind.clone(), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Push] Event in servernamepduids not found in db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
}
|
||||
SendingEventType::Edu(_) => {
|
||||
// Push gateways don't need EDUs (?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pdu in pdus {
|
||||
// Redacted events are not notification targets (we don't send push for them)
|
||||
@@ -427,13 +522,13 @@ impl Sending {
|
||||
let userid =
|
||||
UserId::try_from(utils::string_from_bytes(user).map_err(|_| {
|
||||
(
|
||||
OutgoingKind::Push(user.clone(), pushkey.clone()),
|
||||
kind.clone(),
|
||||
Error::bad_database("Invalid push user string in db."),
|
||||
)
|
||||
})?)
|
||||
.map_err(|_| {
|
||||
(
|
||||
OutgoingKind::Push(user.clone(), pushkey.clone()),
|
||||
kind.clone(),
|
||||
Error::bad_database("Invalid push user id in db."),
|
||||
)
|
||||
})?;
|
||||
@@ -473,7 +568,7 @@ impl Sending {
|
||||
&pusher,
|
||||
rules_for_user,
|
||||
&pdu,
|
||||
db,
|
||||
&db,
|
||||
)
|
||||
.await
|
||||
.map(|_response| kind.clone())
|
||||
@@ -484,15 +579,17 @@ impl Sending {
|
||||
Ok(OutgoingKind::Push(user.clone(), pushkey.clone()))
|
||||
}
|
||||
OutgoingKind::Normal(server) => {
|
||||
let pdu_jsons = pdu_ids
|
||||
.iter()
|
||||
.map(|pdu_id| {
|
||||
Ok::<_, (OutgoingKind, Error)>(
|
||||
let mut edu_jsons = Vec::new();
|
||||
let mut pdu_jsons = Vec::new();
|
||||
|
||||
for event in &events {
|
||||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
// TODO: check room version and remove event_id if needed
|
||||
serde_json::from_str(
|
||||
pdu_jsons.push(serde_json::from_str(
|
||||
PduEvent::convert_to_outgoing_federation_event(
|
||||
db.rooms
|
||||
.get_pdu_json_from_id(pdu_id)
|
||||
.get_pdu_json_from_id(&pdu_id)
|
||||
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
@@ -506,11 +603,15 @@ impl Sending {
|
||||
.json()
|
||||
.get(),
|
||||
)
|
||||
.expect("Raw<..> is always valid"),
|
||||
)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<_>>();
|
||||
.expect("Raw<..> is always valid"));
|
||||
}
|
||||
SendingEventType::Edu(edu) => {
|
||||
edu_jsons.push(
|
||||
serde_json::from_slice(edu).expect("Raw<..> is always valid"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let permit = db.sending.maximum_requests.acquire().await;
|
||||
|
||||
@@ -520,10 +621,17 @@ impl Sending {
|
||||
send_transaction_message::v1::Request {
|
||||
origin: db.globals.server_name(),
|
||||
pdus: &pdu_jsons,
|
||||
edus: &[],
|
||||
origin_server_ts: SystemTime::now(),
|
||||
edus: &edu_jsons,
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
transaction_id: &base64::encode_config(
|
||||
Self::calculate_hash(&pdu_ids),
|
||||
Self::calculate_hash(
|
||||
&events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
),
|
||||
},
|
||||
@@ -546,13 +654,13 @@ impl Sending {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_servercurrentpdus(key: &IVec) -> Result<(OutgoingKind, IVec)> {
|
||||
fn parse_servercurrentevent(key: &[u8]) -> Result<(OutgoingKind, SendingEventType)> {
|
||||
// Appservices start with a plus
|
||||
Ok::<_, Error>(if key.starts_with(b"+") {
|
||||
let mut parts = key[1..].splitn(2, |&b| b == 0xff);
|
||||
|
||||
let server = parts.next().expect("splitn always returns one element");
|
||||
let pdu = parts
|
||||
let event = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
|
||||
let server = utils::string_from_bytes(&server).map_err(|_| {
|
||||
@@ -563,7 +671,11 @@ impl Sending {
|
||||
OutgoingKind::Appservice(Box::<ServerName>::try_from(server).map_err(|_| {
|
||||
Error::bad_database("Invalid server string in server_currenttransaction")
|
||||
})?),
|
||||
IVec::from(pdu),
|
||||
if event.starts_with(b"*") {
|
||||
SendingEventType::Edu(event[1..].to_vec())
|
||||
} else {
|
||||
SendingEventType::Pdu(event.to_vec())
|
||||
},
|
||||
)
|
||||
} else if key.starts_with(b"$") {
|
||||
let mut parts = key[1..].splitn(3, |&b| b == 0xff);
|
||||
@@ -572,18 +684,22 @@ impl Sending {
|
||||
let pushkey = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
|
||||
let pdu = parts
|
||||
let event = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
|
||||
(
|
||||
OutgoingKind::Push(user.to_vec(), pushkey.to_vec()),
|
||||
IVec::from(pdu),
|
||||
if event.starts_with(b"*") {
|
||||
SendingEventType::Edu(event[1..].to_vec())
|
||||
} else {
|
||||
SendingEventType::Pdu(event.to_vec())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let mut parts = key.splitn(2, |&b| b == 0xff);
|
||||
|
||||
let server = parts.next().expect("splitn always returns one element");
|
||||
let pdu = parts
|
||||
let event = parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
|
||||
let server = utils::string_from_bytes(&server).map_err(|_| {
|
||||
@@ -594,7 +710,11 @@ impl Sending {
|
||||
OutgoingKind::Normal(Box::<ServerName>::try_from(server).map_err(|_| {
|
||||
Error::bad_database("Invalid server string in server_currenttransaction")
|
||||
})?),
|
||||
IVec::from(pdu),
|
||||
if event.starts_with(b"*") {
|
||||
SendingEventType::Edu(event[1..].to_vec())
|
||||
} else {
|
||||
SendingEventType::Pdu(event.to_vec())
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Result;
|
||||
use ruma::{DeviceId, UserId};
|
||||
use sled::IVec;
|
||||
|
||||
#[derive(Clone)]
|
||||
use super::abstraction::Tree;
|
||||
|
||||
pub struct TransactionIds {
|
||||
pub(super) userdevicetxnid_response: sled::Tree, // Response can be empty (/sendToDevice) or the event id (/send)
|
||||
pub(super) userdevicetxnid_response: Arc<dyn Tree>, // Response can be empty (/sendToDevice) or the event id (/send)
|
||||
}
|
||||
|
||||
impl TransactionIds {
|
||||
@@ -21,7 +23,7 @@ impl TransactionIds {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(txn_id.as_bytes());
|
||||
|
||||
self.userdevicetxnid_response.insert(key, data)?;
|
||||
self.userdevicetxnid_response.insert(&key, data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -31,7 +33,7 @@ impl TransactionIds {
|
||||
user_id: &UserId,
|
||||
device_id: Option<&DeviceId>,
|
||||
txn_id: &str,
|
||||
) -> Result<Option<IVec>> {
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.map(|d| d.as_bytes()).unwrap_or_default());
|
||||
@@ -39,6 +41,6 @@ impl TransactionIds {
|
||||
key.extend_from_slice(txn_id.as_bytes());
|
||||
|
||||
// If there's no entry, this is a new transaction
|
||||
Ok(self.userdevicetxnid_response.get(key)?)
|
||||
self.userdevicetxnid_response.get(&key)
|
||||
}
|
||||
}
|
||||
|
||||
+103
-33
@@ -1,15 +1,20 @@
|
||||
use crate::{Error, Result};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{client_server::SESSION_ID_LENGTH, utils, Error, Result};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::uiaa::{IncomingAuthData, UiaaInfo},
|
||||
},
|
||||
signatures::CanonicalJsonValue,
|
||||
DeviceId, UserId,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
use super::abstraction::Tree;
|
||||
|
||||
pub struct Uiaa {
|
||||
pub(super) userdeviceid_uiaainfo: sled::Tree, // User-interactive authentication
|
||||
pub(super) userdevicesessionid_uiaainfo: Arc<dyn Tree>, // User-interactive authentication
|
||||
pub(super) userdevicesessionid_uiaarequest: Arc<dyn Tree>, // UiaaRequest = canonical json value
|
||||
}
|
||||
|
||||
impl Uiaa {
|
||||
@@ -19,8 +24,20 @@ impl Uiaa {
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
uiaainfo: &UiaaInfo,
|
||||
json_body: &CanonicalJsonValue,
|
||||
) -> Result<()> {
|
||||
self.update_uiaa_session(user_id, device_id, Some(uiaainfo))
|
||||
self.set_uiaa_request(
|
||||
user_id,
|
||||
device_id,
|
||||
uiaainfo.session.as_ref().expect("session should be set"), // TODO: better session error handling (why is it optional in ruma?)
|
||||
json_body,
|
||||
)?;
|
||||
self.update_uiaa_session(
|
||||
user_id,
|
||||
device_id,
|
||||
uiaainfo.session.as_ref().expect("session should be set"),
|
||||
Some(uiaainfo),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_auth(
|
||||
@@ -40,11 +57,13 @@ impl Uiaa {
|
||||
{
|
||||
let mut uiaainfo = session
|
||||
.as_ref()
|
||||
.map(|session| {
|
||||
Ok::<_, Error>(self.get_uiaa_session(&user_id, &device_id, session)?)
|
||||
})
|
||||
.map(|session| self.get_uiaa_session(&user_id, &device_id, session))
|
||||
.unwrap_or_else(|| Ok(uiaainfo.clone()))?;
|
||||
|
||||
if uiaainfo.session.is_none() {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
}
|
||||
|
||||
// Find out what the user completed
|
||||
match &**kind {
|
||||
"m.login.password" => {
|
||||
@@ -130,35 +149,96 @@ impl Uiaa {
|
||||
}
|
||||
|
||||
if !completed {
|
||||
self.update_uiaa_session(user_id, device_id, Some(&uiaainfo))?;
|
||||
self.update_uiaa_session(
|
||||
user_id,
|
||||
device_id,
|
||||
uiaainfo.session.as_ref().expect("session is always set"),
|
||||
Some(&uiaainfo),
|
||||
)?;
|
||||
return Ok((false, uiaainfo));
|
||||
}
|
||||
|
||||
// UIAA was successful! Remove this session and return true
|
||||
self.update_uiaa_session(user_id, device_id, None)?;
|
||||
self.update_uiaa_session(
|
||||
user_id,
|
||||
device_id,
|
||||
uiaainfo.session.as_ref().expect("session is always set"),
|
||||
None,
|
||||
)?;
|
||||
Ok((true, uiaainfo))
|
||||
} else {
|
||||
panic!("FallbackAcknowledgement is not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
fn set_uiaa_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
session: &str,
|
||||
request: &CanonicalJsonValue,
|
||||
) -> Result<()> {
|
||||
let mut userdevicesessionid = user_id.as_bytes().to_vec();
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(device_id.as_bytes());
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(session.as_bytes());
|
||||
|
||||
self.userdevicesessionid_uiaarequest.insert(
|
||||
&userdevicesessionid,
|
||||
&serde_json::to_vec(request).expect("json value to vec always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_uiaa_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
session: &str,
|
||||
) -> Result<Option<CanonicalJsonValue>> {
|
||||
let mut userdevicesessionid = user_id.as_bytes().to_vec();
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(device_id.as_bytes());
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(session.as_bytes());
|
||||
|
||||
self.userdevicesessionid_uiaarequest
|
||||
.get(&userdevicesessionid)?
|
||||
.map_or(Ok(None), |bytes| {
|
||||
Ok::<_, Error>(Some(
|
||||
serde_json::from_str::<CanonicalJsonValue>(
|
||||
&utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("Invalid uiaa request bytes in db.")
|
||||
})?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("Invalid uiaa request in db."))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn update_uiaa_session(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
session: &str,
|
||||
uiaainfo: Option<&UiaaInfo>,
|
||||
) -> Result<()> {
|
||||
let mut userdeviceid = user_id.as_bytes().to_vec();
|
||||
userdeviceid.push(0xff);
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
let mut userdevicesessionid = user_id.as_bytes().to_vec();
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(device_id.as_bytes());
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(session.as_bytes());
|
||||
|
||||
if let Some(uiaainfo) = uiaainfo {
|
||||
self.userdeviceid_uiaainfo.insert(
|
||||
&userdeviceid,
|
||||
&*serde_json::to_string(&uiaainfo).expect("UiaaInfo::to_string always works"),
|
||||
self.userdevicesessionid_uiaainfo.insert(
|
||||
&userdevicesessionid,
|
||||
&serde_json::to_vec(&uiaainfo).expect("UiaaInfo::to_vec always works"),
|
||||
)?;
|
||||
} else {
|
||||
self.userdeviceid_uiaainfo.remove(&userdeviceid)?;
|
||||
self.userdevicesessionid_uiaainfo
|
||||
.remove(&userdevicesessionid)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -170,14 +250,16 @@ impl Uiaa {
|
||||
device_id: &DeviceId,
|
||||
session: &str,
|
||||
) -> Result<UiaaInfo> {
|
||||
let mut userdeviceid = user_id.as_bytes().to_vec();
|
||||
userdeviceid.push(0xff);
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
let mut userdevicesessionid = user_id.as_bytes().to_vec();
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(device_id.as_bytes());
|
||||
userdevicesessionid.push(0xff);
|
||||
userdevicesessionid.extend_from_slice(session.as_bytes());
|
||||
|
||||
let uiaainfo = serde_json::from_slice::<UiaaInfo>(
|
||||
&self
|
||||
.userdeviceid_uiaainfo
|
||||
.get(&userdeviceid)?
|
||||
.userdevicesessionid_uiaainfo
|
||||
.get(&userdevicesessionid)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"UIAA session does not exist.",
|
||||
@@ -185,18 +267,6 @@ impl Uiaa {
|
||||
)
|
||||
.map_err(|_| Error::bad_database("UiaaInfo in userdeviceid_uiaainfo is invalid."))?;
|
||||
|
||||
if uiaainfo
|
||||
.session
|
||||
.as_ref()
|
||||
.filter(|&s| s == session)
|
||||
.is_none()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"UIAA session token invalid.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(uiaainfo)
|
||||
}
|
||||
}
|
||||
|
||||
+146
-164
@@ -1,52 +1,47 @@
|
||||
use crate::{utils, Error, Result};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
device::Device,
|
||||
keys::{CrossSigningKey, OneTimeKey},
|
||||
},
|
||||
},
|
||||
encryption::DeviceKeys,
|
||||
api::client::{error::ErrorKind, r0::device::Device},
|
||||
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
||||
events::{AnyToDeviceEvent, EventType},
|
||||
identifiers::MxcUri,
|
||||
serde::Raw,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UInt, UserId,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, UInt, UserId,
|
||||
};
|
||||
use std::{collections::BTreeMap, convert::TryFrom, mem, time::SystemTime};
|
||||
use std::{collections::BTreeMap, convert::TryFrom, mem, sync::Arc};
|
||||
|
||||
use super::abstraction::Tree;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Users {
|
||||
pub(super) userid_password: sled::Tree,
|
||||
pub(super) userid_displayname: sled::Tree,
|
||||
pub(super) userid_avatarurl: sled::Tree,
|
||||
pub(super) userdeviceid_token: sled::Tree,
|
||||
pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists
|
||||
pub(super) userid_devicelistversion: sled::Tree, // DevicelistVersion = u64
|
||||
pub(super) token_userdeviceid: sled::Tree,
|
||||
pub(super) userid_password: Arc<dyn Tree>,
|
||||
pub(super) userid_displayname: Arc<dyn Tree>,
|
||||
pub(super) userid_avatarurl: Arc<dyn Tree>,
|
||||
pub(super) userdeviceid_token: Arc<dyn Tree>,
|
||||
pub(super) userdeviceid_metadata: Arc<dyn Tree>, // This is also used to check if a device exists
|
||||
pub(super) userid_devicelistversion: Arc<dyn Tree>, // DevicelistVersion = u64
|
||||
pub(super) token_userdeviceid: Arc<dyn Tree>,
|
||||
|
||||
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + DeviceKeyId
|
||||
pub(super) userid_lastonetimekeyupdate: sled::Tree, // LastOneTimeKeyUpdate = Count
|
||||
pub(super) keychangeid_userid: sled::Tree, // KeyChangeId = UserId/RoomId + Count
|
||||
pub(super) keyid_key: sled::Tree, // KeyId = UserId + KeyId (depends on key type)
|
||||
pub(super) userid_masterkeyid: sled::Tree,
|
||||
pub(super) userid_selfsigningkeyid: sled::Tree,
|
||||
pub(super) userid_usersigningkeyid: sled::Tree,
|
||||
pub(super) onetimekeyid_onetimekeys: Arc<dyn Tree>, // OneTimeKeyId = UserId + DeviceKeyId
|
||||
pub(super) userid_lastonetimekeyupdate: Arc<dyn Tree>, // LastOneTimeKeyUpdate = Count
|
||||
pub(super) keychangeid_userid: Arc<dyn Tree>, // KeyChangeId = UserId/RoomId + Count
|
||||
pub(super) keyid_key: Arc<dyn Tree>, // KeyId = UserId + KeyId (depends on key type)
|
||||
pub(super) userid_masterkeyid: Arc<dyn Tree>,
|
||||
pub(super) userid_selfsigningkeyid: Arc<dyn Tree>,
|
||||
pub(super) userid_usersigningkeyid: Arc<dyn Tree>,
|
||||
|
||||
pub(super) todeviceid_events: sled::Tree, // ToDeviceId = UserId + DeviceId + Count
|
||||
pub(super) todeviceid_events: Arc<dyn Tree>, // ToDeviceId = UserId + DeviceId + Count
|
||||
}
|
||||
|
||||
impl Users {
|
||||
/// Check if a user has an account on this homeserver.
|
||||
pub fn exists(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.userid_password.contains_key(user_id.to_string())?)
|
||||
Ok(self.userid_password.get(user_id.as_bytes())?.is_some())
|
||||
}
|
||||
|
||||
/// Check if account is deactivated
|
||||
pub fn is_deactivated(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self
|
||||
.userid_password
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User does not exist.",
|
||||
@@ -55,20 +50,20 @@ impl Users {
|
||||
}
|
||||
|
||||
/// Create a new user account on this homeserver.
|
||||
pub fn create(&self, user_id: &UserId, password: &str) -> Result<()> {
|
||||
pub fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||
self.set_password(user_id, password)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the number of users registered on this server.
|
||||
pub fn count(&self) -> usize {
|
||||
self.userid_password.iter().count()
|
||||
pub fn count(&self) -> Result<usize> {
|
||||
Ok(self.userid_password.iter().count())
|
||||
}
|
||||
|
||||
/// Find out which user an access token belongs to.
|
||||
pub fn find_from_token(&self, token: &str) -> Result<Option<(UserId, String)>> {
|
||||
self.token_userdeviceid
|
||||
.get(token)?
|
||||
.get(token.as_bytes())?
|
||||
.map_or(Ok(None), |bytes| {
|
||||
let mut parts = bytes.split(|&b| b == 0xff);
|
||||
let user_bytes = parts.next().ok_or_else(|| {
|
||||
@@ -93,21 +88,19 @@ impl Users {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all users on this homeserver.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.userid_password.iter().keys().map(|bytes| {
|
||||
Ok(
|
||||
UserId::try_from(utils::string_from_bytes(&bytes?).map_err(|_| {
|
||||
Error::bad_database("User ID in userid_password is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in userid_password is invalid."))?,
|
||||
)
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> + '_ {
|
||||
self.userid_password.iter().map(|(bytes, _)| {
|
||||
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("User ID in userid_password is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in userid_password is invalid."))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the password hash for the given user.
|
||||
pub fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
self.userid_password
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("Password hash in db is not valid string.")
|
||||
@@ -116,22 +109,28 @@ impl Users {
|
||||
}
|
||||
|
||||
/// Hash and set the user's password to the Argon2 hash
|
||||
pub fn set_password(&self, user_id: &UserId, password: &str) -> Result<()> {
|
||||
if let Ok(hash) = utils::calculate_hash(&password) {
|
||||
self.userid_password.insert(user_id.to_string(), &*hash)?;
|
||||
Ok(())
|
||||
pub fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||
if let Some(password) = password {
|
||||
if let Ok(hash) = utils::calculate_hash(&password) {
|
||||
self.userid_password
|
||||
.insert(user_id.as_bytes(), hash.as_bytes())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Password does not meet the requirements.",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Password does not meet the requirements.",
|
||||
))
|
||||
self.userid_password.insert(user_id.as_bytes(), b"")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the displayname of a user on this homeserver.
|
||||
pub fn displayname(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
self.userid_displayname
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("Displayname in db is invalid.")
|
||||
@@ -143,9 +142,9 @@ impl Users {
|
||||
pub fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()> {
|
||||
if let Some(displayname) = displayname {
|
||||
self.userid_displayname
|
||||
.insert(user_id.to_string(), &*displayname)?;
|
||||
.insert(user_id.as_bytes(), displayname.as_bytes())?;
|
||||
} else {
|
||||
self.userid_displayname.remove(user_id.to_string())?;
|
||||
self.userid_displayname.remove(user_id.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -154,7 +153,7 @@ impl Users {
|
||||
/// Get a the avatar_url of a user.
|
||||
pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<MxcUri>> {
|
||||
self.userid_avatarurl
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
let s = utils::string_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
|
||||
@@ -167,9 +166,9 @@ impl Users {
|
||||
pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<MxcUri>) -> Result<()> {
|
||||
if let Some(avatar_url) = avatar_url {
|
||||
self.userid_avatarurl
|
||||
.insert(user_id.to_string(), avatar_url.to_string().as_str())?;
|
||||
.insert(user_id.as_bytes(), avatar_url.to_string().as_bytes())?;
|
||||
} else {
|
||||
self.userid_avatarurl.remove(user_id.to_string())?;
|
||||
self.userid_avatarurl.remove(user_id.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -191,19 +190,17 @@ impl Users {
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
self.userid_devicelistversion
|
||||
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
|
||||
.expect("utils::increment will always put in a value");
|
||||
.increment(user_id.as_bytes())?;
|
||||
|
||||
self.userdeviceid_metadata.insert(
|
||||
userdeviceid,
|
||||
serde_json::to_string(&Device {
|
||||
&userdeviceid,
|
||||
&serde_json::to_vec(&Device {
|
||||
device_id: device_id.into(),
|
||||
display_name: initial_device_display_name,
|
||||
last_seen_ip: None, // TODO
|
||||
last_seen_ts: Some(SystemTime::now()),
|
||||
last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()),
|
||||
})
|
||||
.expect("Device::to_string never fails.")
|
||||
.as_bytes(),
|
||||
.expect("Device::to_string never fails."),
|
||||
)?;
|
||||
|
||||
self.set_token(user_id, &device_id, token)?;
|
||||
@@ -218,7 +215,8 @@ impl Users {
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
// Remove tokens
|
||||
if let Some(old_token) = self.userdeviceid_token.remove(&userdeviceid)? {
|
||||
if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? {
|
||||
self.userdeviceid_token.remove(&userdeviceid)?;
|
||||
self.token_userdeviceid.remove(&old_token)?;
|
||||
}
|
||||
|
||||
@@ -226,15 +224,14 @@ impl Users {
|
||||
let mut prefix = userdeviceid.clone();
|
||||
prefix.push(0xff);
|
||||
|
||||
for key in self.todeviceid_events.scan_prefix(&prefix).keys() {
|
||||
self.todeviceid_events.remove(key?)?;
|
||||
for (key, _) in self.todeviceid_events.scan_prefix(prefix) {
|
||||
self.todeviceid_events.remove(&key)?;
|
||||
}
|
||||
|
||||
// TODO: Remove onetimekeys
|
||||
|
||||
self.userid_devicelistversion
|
||||
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
|
||||
.expect("utils::increment will always put in a value");
|
||||
.increment(user_id.as_bytes())?;
|
||||
|
||||
self.userdeviceid_metadata.remove(&userdeviceid)?;
|
||||
|
||||
@@ -242,16 +239,18 @@ impl Users {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all device ids of this user.
|
||||
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<Box<DeviceId>>> {
|
||||
pub fn all_device_ids<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> impl Iterator<Item = Result<Box<DeviceId>>> + 'a {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
// All devices have metadata
|
||||
self.userdeviceid_metadata
|
||||
.scan_prefix(prefix)
|
||||
.keys()
|
||||
.map(|bytes| {
|
||||
.map(|(bytes, _)| {
|
||||
Ok(utils::string_from_bytes(
|
||||
&*bytes?
|
||||
&bytes
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("UserDevice ID in db is invalid."))?,
|
||||
@@ -272,13 +271,15 @@ impl Users {
|
||||
|
||||
// Remove old token
|
||||
if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? {
|
||||
self.token_userdeviceid.remove(old_token)?;
|
||||
self.token_userdeviceid.remove(&old_token)?;
|
||||
// It will be removed from userdeviceid_token by the insert later
|
||||
}
|
||||
|
||||
// Assign token to user device combination
|
||||
self.userdeviceid_token.insert(&userdeviceid, &*token)?;
|
||||
self.token_userdeviceid.insert(token, userdeviceid)?;
|
||||
self.userdeviceid_token
|
||||
.insert(&userdeviceid, token.as_bytes())?;
|
||||
self.token_userdeviceid
|
||||
.insert(token.as_bytes(), &userdeviceid)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -310,8 +311,7 @@ impl Users {
|
||||
|
||||
self.onetimekeyid_onetimekeys.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&one_time_key_value)
|
||||
.expect("OneTimeKey::to_string always works"),
|
||||
&serde_json::to_vec(&one_time_key_value).expect("OneTimeKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
self.userid_lastonetimekeyupdate
|
||||
@@ -351,10 +351,9 @@ impl Users {
|
||||
.insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
|
||||
|
||||
self.onetimekeyid_onetimekeys
|
||||
.scan_prefix(&prefix)
|
||||
.scan_prefix(prefix)
|
||||
.next()
|
||||
.map(|r| {
|
||||
let (key, value) = r?;
|
||||
.map(|(key, value)| {
|
||||
self.onetimekeyid_onetimekeys.remove(&key)?;
|
||||
|
||||
Ok((
|
||||
@@ -384,21 +383,20 @@ impl Users {
|
||||
|
||||
let mut counts = BTreeMap::new();
|
||||
|
||||
for algorithm in self
|
||||
.onetimekeyid_onetimekeys
|
||||
.scan_prefix(&userdeviceid)
|
||||
.keys()
|
||||
.map(|bytes| {
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_slice::<DeviceKeyId>(
|
||||
&*bytes?.rsplit(|&b| b == 0xff).next().ok_or_else(|| {
|
||||
Error::bad_database("OneTimeKey ID in db is invalid.")
|
||||
})?,
|
||||
for algorithm in
|
||||
self.onetimekeyid_onetimekeys
|
||||
.scan_prefix(userdeviceid)
|
||||
.map(|(bytes, _)| {
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_slice::<DeviceKeyId>(
|
||||
&*bytes.rsplit(|&b| b == 0xff).next().ok_or_else(|| {
|
||||
Error::bad_database("OneTimeKey ID in db is invalid.")
|
||||
})?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))?
|
||||
.algorithm(),
|
||||
)
|
||||
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))?
|
||||
.algorithm(),
|
||||
)
|
||||
})
|
||||
})
|
||||
{
|
||||
*counts.entry(algorithm?).or_default() += UInt::from(1_u32);
|
||||
}
|
||||
@@ -420,7 +418,7 @@ impl Users {
|
||||
|
||||
self.keyid_key.insert(
|
||||
&userdeviceid,
|
||||
&*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"),
|
||||
&serde_json::to_vec(&device_keys).expect("DeviceKeys::to_vec always works"),
|
||||
)?;
|
||||
|
||||
self.mark_device_key_update(user_id, rooms, globals)?;
|
||||
@@ -461,11 +459,11 @@ impl Users {
|
||||
|
||||
self.keyid_key.insert(
|
||||
&master_key_key,
|
||||
&*serde_json::to_string(&master_key).expect("CrossSigningKey::to_string always works"),
|
||||
&serde_json::to_vec(&master_key).expect("CrossSigningKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
self.userid_masterkeyid
|
||||
.insert(&*user_id.to_string(), master_key_key)?;
|
||||
.insert(user_id.as_bytes(), &master_key_key)?;
|
||||
|
||||
// Self-signing key
|
||||
if let Some(self_signing_key) = self_signing_key {
|
||||
@@ -487,12 +485,12 @@ impl Users {
|
||||
|
||||
self.keyid_key.insert(
|
||||
&self_signing_key_key,
|
||||
&*serde_json::to_string(&self_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
&serde_json::to_vec(&self_signing_key)
|
||||
.expect("CrossSigningKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
self.userid_selfsigningkeyid
|
||||
.insert(&*user_id.to_string(), self_signing_key_key)?;
|
||||
.insert(user_id.as_bytes(), &self_signing_key_key)?;
|
||||
}
|
||||
|
||||
// User-signing key
|
||||
@@ -515,12 +513,12 @@ impl Users {
|
||||
|
||||
self.keyid_key.insert(
|
||||
&user_signing_key_key,
|
||||
&*serde_json::to_string(&user_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
&serde_json::to_vec(&user_signing_key)
|
||||
.expect("CrossSigningKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
self.userid_usersigningkeyid
|
||||
.insert(&*user_id.to_string(), user_signing_key_key)?;
|
||||
.insert(user_id.as_bytes(), &user_signing_key_key)?;
|
||||
}
|
||||
|
||||
self.mark_device_key_update(user_id, rooms, globals)?;
|
||||
@@ -562,8 +560,7 @@ impl Users {
|
||||
|
||||
self.keyid_key.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&cross_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
&serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
// TODO: Should we notify about this change?
|
||||
@@ -573,36 +570,26 @@ impl Users {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn keys_changed(
|
||||
&self,
|
||||
pub fn keys_changed<'a>(
|
||||
&'a self,
|
||||
user_or_room_id: &str,
|
||||
from: u64,
|
||||
to: Option<u64>,
|
||||
) -> impl Iterator<Item = Result<UserId>> {
|
||||
) -> impl Iterator<Item = Result<UserId>> + 'a {
|
||||
let mut prefix = user_or_room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut start = prefix.clone();
|
||||
start.extend_from_slice(&(from + 1).to_be_bytes());
|
||||
|
||||
let mut end = prefix.clone();
|
||||
end.extend_from_slice(&to.unwrap_or(u64::MAX).to_be_bytes());
|
||||
|
||||
self.keychangeid_userid
|
||||
.range(start..end)
|
||||
.filter_map(|r| r.ok())
|
||||
.iter_from(&start, false)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, bytes)| {
|
||||
Ok(
|
||||
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"User ID in devicekeychangeid_userid is invalid unicode.",
|
||||
)
|
||||
})?)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("User ID in devicekeychangeid_userid is invalid.")
|
||||
})?,
|
||||
)
|
||||
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("User ID in devicekeychangeid_userid is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in devicekeychangeid_userid is invalid."))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -626,13 +613,13 @@ impl Users {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
|
||||
}
|
||||
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -646,23 +633,22 @@ impl Users {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database("DeviceKeys in db are invalid.")
|
||||
})?))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_master_key(
|
||||
pub fn get_master_key<F: Fn(&UserId) -> bool>(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
sender_id: &UserId,
|
||||
allowed_signatures: F,
|
||||
) -> Result<Option<CrossSigningKey>> {
|
||||
// TODO: hide some signatures
|
||||
self.userid_masterkeyid
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
@@ -673,7 +659,7 @@ impl Users {
|
||||
cross_signing_key.signatures = cross_signing_key
|
||||
.signatures
|
||||
.into_iter()
|
||||
.filter(|(user, _)| user == user_id || user == sender_id)
|
||||
.filter(|(user, _)| allowed_signatures(user))
|
||||
.collect();
|
||||
|
||||
Ok(Some(cross_signing_key))
|
||||
@@ -681,15 +667,15 @@ impl Users {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_self_signing_key(
|
||||
pub fn get_self_signing_key<F: Fn(&UserId) -> bool>(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
sender_id: &UserId,
|
||||
allowed_signatures: F,
|
||||
) -> Result<Option<CrossSigningKey>> {
|
||||
self.userid_selfsigningkeyid
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
@@ -700,7 +686,7 @@ impl Users {
|
||||
cross_signing_key.signatures = cross_signing_key
|
||||
.signatures
|
||||
.into_iter()
|
||||
.filter(|(user, _)| user == user_id || user == sender_id)
|
||||
.filter(|(user, _)| user == user_id || allowed_signatures(user))
|
||||
.collect();
|
||||
|
||||
Ok(Some(cross_signing_key))
|
||||
@@ -710,9 +696,9 @@ impl Users {
|
||||
|
||||
pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<CrossSigningKey>> {
|
||||
self.userid_usersigningkeyid
|
||||
.get(user_id.to_string())?
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
})?))
|
||||
@@ -725,7 +711,7 @@ impl Users {
|
||||
sender: &UserId,
|
||||
target_user_id: &UserId,
|
||||
target_device_id: &DeviceId,
|
||||
event_type: &EventType,
|
||||
event_type: &str,
|
||||
content: serde_json::Value,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
@@ -736,14 +722,13 @@ impl Users {
|
||||
key.extend_from_slice(&globals.next_count()?.to_be_bytes());
|
||||
|
||||
let mut json = serde_json::Map::new();
|
||||
json.insert("type".to_owned(), event_type.to_string().into());
|
||||
json.insert("type".to_owned(), event_type.to_owned().into());
|
||||
json.insert("sender".to_owned(), sender.to_string().into());
|
||||
json.insert("content".to_owned(), content);
|
||||
|
||||
self.todeviceid_events.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&json).expect("Map::to_string always works"),
|
||||
)?;
|
||||
let value = serde_json::to_vec(&json).expect("Map::to_vec always works");
|
||||
|
||||
self.todeviceid_events.insert(&key, &value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -761,9 +746,9 @@ impl Users {
|
||||
prefix.extend_from_slice(device_id.as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
for value in self.todeviceid_events.scan_prefix(&prefix).values() {
|
||||
for (_, value) in self.todeviceid_events.scan_prefix(prefix) {
|
||||
events.push(
|
||||
serde_json::from_slice(&*value?)
|
||||
serde_json::from_slice(&value)
|
||||
.map_err(|_| Error::bad_database("Event in todeviceid_events is invalid."))?,
|
||||
);
|
||||
}
|
||||
@@ -788,10 +773,9 @@ impl Users {
|
||||
|
||||
for (key, _) in self
|
||||
.todeviceid_events
|
||||
.range(&*prefix..=&*last)
|
||||
.keys()
|
||||
.map(|key| {
|
||||
let key = key?;
|
||||
.iter_from(&last, true) // this includes last
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(key, _)| {
|
||||
Ok::<_, Error>((
|
||||
key.clone(),
|
||||
utils::u64_from_bytes(&key[key.len() - mem::size_of::<u64>()..key.len()])
|
||||
@@ -801,7 +785,7 @@ impl Users {
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|&(_, count)| count <= until)
|
||||
{
|
||||
self.todeviceid_events.remove(key)?;
|
||||
self.todeviceid_events.remove(&key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -821,14 +805,11 @@ impl Users {
|
||||
assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some());
|
||||
|
||||
self.userid_devicelistversion
|
||||
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
|
||||
.expect("utils::increment will always put in a value");
|
||||
.increment(user_id.as_bytes())?;
|
||||
|
||||
self.userdeviceid_metadata.insert(
|
||||
userdeviceid,
|
||||
serde_json::to_string(device)
|
||||
.expect("Device::to_string always works")
|
||||
.as_bytes(),
|
||||
&userdeviceid,
|
||||
&serde_json::to_vec(device).expect("Device::to_string always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -863,17 +844,18 @@ impl Users {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> {
|
||||
pub fn all_devices_metadata<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> impl Iterator<Item = Result<Device>> + 'a {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
|
||||
self.userdeviceid_metadata
|
||||
.scan_prefix(key)
|
||||
.values()
|
||||
.map(|bytes| {
|
||||
Ok(serde_json::from_slice::<Device>(&bytes?).map_err(|_| {
|
||||
Error::bad_database("Device in userdeviceid_metadata is invalid.")
|
||||
})?)
|
||||
.map(|(_, bytes)| {
|
||||
serde_json::from_slice::<Device>(&bytes)
|
||||
.map_err(|_| Error::bad_database("Device in userdeviceid_metadata is invalid."))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -887,7 +869,7 @@ impl Users {
|
||||
// Set the password to "" to indicate a deactivated account. Hashes will never result in an
|
||||
// empty string, so the user will not be able to log in again. Systems like changing the
|
||||
// password without logging in should check if the account is deactivated.
|
||||
self.userid_password.insert(user_id.to_string(), "")?;
|
||||
self.userid_password.insert(user_id.as_bytes(), &[])?;
|
||||
|
||||
// TODO: Unhook 3PID
|
||||
Ok(())
|
||||
|
||||
+54
-16
@@ -1,5 +1,11 @@
|
||||
use log::error;
|
||||
use ruma::api::client::{error::ErrorKind, r0::uiaa::UiaaInfo};
|
||||
use log::{error, warn};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::{Error as RumaError, ErrorKind},
|
||||
r0::uiaa::UiaaInfo,
|
||||
},
|
||||
ServerName,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@@ -10,18 +16,31 @@ use {
|
||||
response::{self, Responder},
|
||||
Request,
|
||||
},
|
||||
ruma::api::client::{error::Error as RumaError, r0::uiaa::UiaaResponse},
|
||||
ruma::api::client::r0::uiaa::UiaaResponse,
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("There was a problem with the connection to the database.")]
|
||||
#[cfg(feature = "sled")]
|
||||
#[error("There was a problem with the connection to the sled database.")]
|
||||
SledError {
|
||||
#[from]
|
||||
source: sled::Error,
|
||||
},
|
||||
#[cfg(feature = "rocksdb")]
|
||||
#[error("There was a problem with the connection to the rocksdb database: {source}")]
|
||||
RocksDbError {
|
||||
#[from]
|
||||
source: rocksdb::Error,
|
||||
},
|
||||
#[cfg(feature = "sqlite")]
|
||||
#[error("There was a problem with the connection to the sqlite database: {source}")]
|
||||
SqliteError {
|
||||
#[from]
|
||||
source: rusqlite::Error,
|
||||
},
|
||||
#[error("Could not generate an image.")]
|
||||
ImageError {
|
||||
#[from]
|
||||
@@ -33,6 +52,13 @@ pub enum Error {
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
FederationError(Box<ServerName>, RumaError),
|
||||
#[error("Could not do this io: {source}")]
|
||||
IoError {
|
||||
#[from]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
BadServerResponse(&'static str),
|
||||
#[error("{0}")]
|
||||
BadConfig(&'static str),
|
||||
@@ -41,7 +67,6 @@ pub enum Error {
|
||||
BadDatabase(&'static str),
|
||||
#[error("uiaa")]
|
||||
Uiaa(UiaaInfo),
|
||||
|
||||
#[error("{0}: {1}")]
|
||||
BadRequest(ErrorKind, &'static str),
|
||||
#[error("{0}")]
|
||||
@@ -60,14 +85,16 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
impl<'r, 'o> Responder<'r, 'o> for Error
|
||||
where
|
||||
'o: 'r,
|
||||
{
|
||||
fn respond_to(self, r: &'r Request<'_>) -> response::Result<'o> {
|
||||
if let Self::Uiaa(uiaainfo) = &self {
|
||||
return RumaResponse::from(UiaaResponse::AuthResponse(uiaainfo.clone())).respond_to(r);
|
||||
impl Error {
|
||||
pub fn to_response(&self) -> RumaResponse<UiaaResponse> {
|
||||
if let Self::Uiaa(uiaainfo) = self {
|
||||
return RumaResponse(UiaaResponse::AuthResponse(uiaainfo.clone()));
|
||||
}
|
||||
|
||||
if let Self::FederationError(origin, error) = self {
|
||||
let mut error = error.clone();
|
||||
error.message = format!("Answer from {}: {}", origin, error.message);
|
||||
return RumaResponse(UiaaResponse::MatrixError(error));
|
||||
}
|
||||
|
||||
let message = format!("{}", self);
|
||||
@@ -92,11 +119,22 @@ where
|
||||
_ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
RumaResponse::from(RumaError {
|
||||
warn!("{}: {}", status_code, message);
|
||||
|
||||
RumaResponse(UiaaResponse::MatrixError(RumaError {
|
||||
kind,
|
||||
message,
|
||||
status_code,
|
||||
})
|
||||
.respond_to(r)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
impl<'r, 'o> Responder<'r, 'o> for Error
|
||||
where
|
||||
'o: 'r,
|
||||
{
|
||||
fn respond_to(self, r: &'r Request<'_>) -> response::Result<'o> {
|
||||
self.to_response().respond_to(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#![allow(clippy::suspicious_else_formatting)]
|
||||
#![deny(clippy::dbg_macro)]
|
||||
|
||||
pub mod appservice_server;
|
||||
pub mod client_server;
|
||||
mod database;
|
||||
|
||||
+24
-10
@@ -1,5 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![allow(clippy::suspicious_else_formatting)]
|
||||
#![deny(clippy::dbg_macro)]
|
||||
|
||||
pub mod appservice_server;
|
||||
pub mod client_server;
|
||||
@@ -11,6 +12,8 @@ mod pdu;
|
||||
mod ruma_wrapper;
|
||||
mod utils;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use database::Config;
|
||||
pub use database::Database;
|
||||
pub use error::{Error, Result};
|
||||
@@ -27,10 +30,11 @@ use rocket::{
|
||||
},
|
||||
routes, Request,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::span;
|
||||
use tracing_subscriber::{prelude::*, Registry};
|
||||
|
||||
fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build> {
|
||||
fn setup_rocket(config: Figment, data: Arc<RwLock<Database>>) -> rocket::Rocket<rocket::Build> {
|
||||
rocket::custom(config)
|
||||
.manage(data)
|
||||
.mount(
|
||||
@@ -68,6 +72,7 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
|
||||
client_server::get_avatar_url_route,
|
||||
client_server::get_profile_route,
|
||||
client_server::set_presence_route,
|
||||
client_server::get_presence_route,
|
||||
client_server::upload_keys_route,
|
||||
client_server::get_keys_route,
|
||||
client_server::claim_keys_route,
|
||||
@@ -149,6 +154,8 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
|
||||
server_server::send_transaction_message_route,
|
||||
server_server::get_event_route,
|
||||
server_server::get_missing_events_route,
|
||||
server_server::get_event_authorization_route,
|
||||
server_server::get_room_state_route,
|
||||
server_server::get_room_state_ids_route,
|
||||
server_server::create_join_event_template_route,
|
||||
server_server::create_join_event_route,
|
||||
@@ -156,6 +163,8 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
|
||||
server_server::get_devices_route,
|
||||
server_server::get_room_information_route,
|
||||
server_server::get_profile_information_route,
|
||||
server_server::get_keys_route,
|
||||
server_server::claim_keys_route,
|
||||
],
|
||||
)
|
||||
.register(
|
||||
@@ -185,15 +194,14 @@ async fn main() {
|
||||
)
|
||||
.merge(Env::prefixed("CONDUIT_").global());
|
||||
|
||||
std::env::set_var("RUST_LOG", "warn");
|
||||
|
||||
let config = raw_config
|
||||
.extract::<Config>()
|
||||
.expect("It looks like your config is invalid. Please take a look at the error");
|
||||
|
||||
let db = Database::load_or_create(config.clone())
|
||||
.await
|
||||
.expect("config is valid");
|
||||
|
||||
db.sending.start_handler(&db);
|
||||
let mut _span: Option<span::Span> = None;
|
||||
let mut _enter: Option<span::Entered<'_>> = None;
|
||||
|
||||
if config.allow_jaeger {
|
||||
let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline()
|
||||
@@ -203,13 +211,19 @@ async fn main() {
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
Registry::default().with(telemetry).try_init().unwrap();
|
||||
|
||||
let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
|
||||
let _enter = root.enter();
|
||||
_span = Some(span!(tracing::Level::INFO, "app_start", work_units = 2));
|
||||
_enter = Some(_span.as_ref().unwrap().enter());
|
||||
} else {
|
||||
std::env::set_var("CONDUIT_LOG", config.log);
|
||||
pretty_env_logger::init_custom_env("CONDUIT_LOG");
|
||||
std::env::set_var("RUST_LOG", &config.log);
|
||||
tracing_subscriber::fmt::init();
|
||||
}
|
||||
|
||||
config.warn_deprecated();
|
||||
|
||||
let db = Database::load_or_create(config)
|
||||
.await
|
||||
.expect("config is valid");
|
||||
|
||||
let rocket = setup_rocket(raw_config, db);
|
||||
rocket.launch().await.unwrap();
|
||||
}
|
||||
|
||||
+14
-12
@@ -2,15 +2,17 @@ use crate::Error;
|
||||
use log::error;
|
||||
use ruma::{
|
||||
events::{
|
||||
pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent,
|
||||
AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, StateEvent,
|
||||
pdu::EventHash, room::member::MemberEventContent, AnyEphemeralRoomEvent, AnyRoomEvent,
|
||||
AnyStateEvent, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType,
|
||||
StateEvent,
|
||||
},
|
||||
serde::{CanonicalJsonObject, CanonicalJsonValue, Raw},
|
||||
EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId,
|
||||
state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName,
|
||||
ServerSigningKeyId, UInt, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH};
|
||||
use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct PduEvent {
|
||||
@@ -72,9 +74,7 @@ impl PduEvent {
|
||||
|
||||
self.unsigned.insert(
|
||||
"redacted_because".to_owned(),
|
||||
serde_json::to_string(reason)
|
||||
.expect("PduEvent::to_string always works")
|
||||
.into(),
|
||||
serde_json::to_value(reason).expect("to_value(PduEvent) always works"),
|
||||
);
|
||||
|
||||
self.content = new_content.into();
|
||||
@@ -105,7 +105,7 @@ impl PduEvent {
|
||||
|
||||
/// This only works for events that are also AnyRoomEvents.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_any_event(&self) -> Raw<AnyEvent> {
|
||||
pub fn to_any_event(&self) -> Raw<AnyEphemeralRoomEvent> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
@@ -213,7 +213,10 @@ impl PduEvent {
|
||||
pub fn convert_to_outgoing_federation_event(
|
||||
mut pdu_json: CanonicalJsonObject,
|
||||
) -> Raw<ruma::events::pdu::Pdu> {
|
||||
if let Some(CanonicalJsonValue::Object(unsigned)) = pdu_json.get_mut("unsigned") {
|
||||
if let Some(unsigned) = pdu_json
|
||||
.get_mut("unsigned")
|
||||
.and_then(|val| val.as_object_mut())
|
||||
{
|
||||
unsigned.remove("transaction_id");
|
||||
}
|
||||
|
||||
@@ -264,10 +267,9 @@ impl state_res::Event for PduEvent {
|
||||
fn content(&self) -> serde_json::Value {
|
||||
self.content.clone()
|
||||
}
|
||||
fn origin_server_ts(&self) -> std::time::SystemTime {
|
||||
UNIX_EPOCH + std::time::Duration::from_millis(self.origin_server_ts.into())
|
||||
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
|
||||
MilliSecondsSinceUnixEpoch(self.origin_server_ts)
|
||||
}
|
||||
|
||||
fn state_key(&self) -> Option<String> {
|
||||
self.state_key.clone()
|
||||
}
|
||||
|
||||
+129
-86
@@ -1,14 +1,15 @@
|
||||
use crate::Error;
|
||||
use crate::{database::DatabaseGuard, Error};
|
||||
use ruma::{
|
||||
api::OutgoingResponse,
|
||||
api::{client::r0::uiaa::UiaaResponse, OutgoingResponse},
|
||||
identifiers::{DeviceId, UserId},
|
||||
Outgoing,
|
||||
signatures::CanonicalJsonValue,
|
||||
Outgoing, ServerName,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use {
|
||||
crate::{server_server, utils},
|
||||
crate::server_server,
|
||||
log::{debug, warn},
|
||||
rocket::{
|
||||
data::{self, ByteUnit, Data, FromData},
|
||||
@@ -16,13 +17,9 @@ use {
|
||||
outcome::Outcome::*,
|
||||
response::{self, Responder},
|
||||
tokio::io::AsyncReadExt,
|
||||
Request, State,
|
||||
},
|
||||
ruma::{
|
||||
api::{AuthScheme, IncomingRequest},
|
||||
signatures::CanonicalJsonValue,
|
||||
ServerName,
|
||||
Request,
|
||||
},
|
||||
ruma::api::{AuthScheme, IncomingRequest},
|
||||
std::collections::BTreeMap,
|
||||
std::convert::TryFrom,
|
||||
std::io::Cursor,
|
||||
@@ -34,8 +31,9 @@ pub struct Ruma<T: Outgoing> {
|
||||
pub body: T::Incoming,
|
||||
pub sender_user: Option<UserId>,
|
||||
pub sender_device: Option<Box<DeviceId>>,
|
||||
pub sender_servername: Option<Box<ServerName>>,
|
||||
// This is None when body is not a valid string
|
||||
pub json_body: Option<Box<serde_json::value::RawValue>>,
|
||||
pub json_body: Option<CanonicalJsonValue>,
|
||||
pub from_appservice: bool,
|
||||
}
|
||||
|
||||
@@ -50,7 +48,7 @@ where
|
||||
async fn from_data(request: &'a Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
|
||||
let metadata = T::Incoming::METADATA;
|
||||
let db = request
|
||||
.guard::<State<'_, crate::Database>>()
|
||||
.guard::<DatabaseGuard>()
|
||||
.await
|
||||
.expect("database was loaded");
|
||||
|
||||
@@ -58,7 +56,7 @@ where
|
||||
let token = request
|
||||
.headers()
|
||||
.get_one("Authorization")
|
||||
.map(|s| s[7..].to_owned()) // Split off "Bearer "
|
||||
.and_then(|s| s.get(7..)) // Split off "Bearer "
|
||||
.or_else(|| request.query_value("access_token").and_then(|r| r.ok()));
|
||||
|
||||
let limit = db.globals.max_request_size();
|
||||
@@ -66,9 +64,15 @@ where
|
||||
let mut body = Vec::new();
|
||||
handle.read_to_end(&mut body).await.unwrap();
|
||||
|
||||
let (sender_user, sender_device, from_appservice) = if let Some((_id, registration)) = db
|
||||
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
|
||||
|
||||
let (sender_user, sender_device, sender_servername, from_appservice) = if let Some((
|
||||
_id,
|
||||
registration,
|
||||
)) = db
|
||||
.appservice
|
||||
.iter_all()
|
||||
.unwrap()
|
||||
.filter_map(|r| r.ok())
|
||||
.find(|(_id, registration)| {
|
||||
registration
|
||||
@@ -102,10 +106,10 @@ where
|
||||
}
|
||||
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
(Some(user_id), None, true)
|
||||
(Some(user_id), None, None, true)
|
||||
}
|
||||
AuthScheme::ServerSignatures => (None, None, true),
|
||||
AuthScheme::None => (None, None, true),
|
||||
AuthScheme::ServerSignatures => (None, None, None, true),
|
||||
AuthScheme::None => (None, None, None, true),
|
||||
}
|
||||
} else {
|
||||
match metadata.authentication {
|
||||
@@ -114,9 +118,12 @@ where
|
||||
match db.users.find_from_token(&token).unwrap() {
|
||||
// Unknown Token
|
||||
None => return Failure((Status::raw(581), ())),
|
||||
Some((user_id, device_id)) => {
|
||||
(Some(user_id), Some(device_id.into()), false)
|
||||
}
|
||||
Some((user_id, device_id)) => (
|
||||
Some(user_id),
|
||||
Some(Box::<DeviceId>::from(device_id)),
|
||||
None,
|
||||
false,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
// Missing Token
|
||||
@@ -125,16 +132,18 @@ where
|
||||
}
|
||||
AuthScheme::ServerSignatures => {
|
||||
// Get origin from header
|
||||
let x_matrix = match request.headers().get_one("Authorization").map(|s| {
|
||||
// Split off "X-Matrix " and parse the rest
|
||||
s[9..]
|
||||
.split_terminator(',')
|
||||
.map(|field| {
|
||||
let mut splits = field.splitn(2, '=');
|
||||
(splits.next(), splits.next().map(|s| s.trim_matches('"')))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
}) {
|
||||
let x_matrix = match request
|
||||
.headers()
|
||||
.get_one("Authorization")
|
||||
.and_then(|s| s.get(9..)) // Split off "X-Matrix " and parse the rest
|
||||
.map(|s| {
|
||||
s.split_terminator(',')
|
||||
.map(|field| {
|
||||
let mut splits = field.splitn(2, '=');
|
||||
(splits.next(), splits.next().map(|s| s.trim_matches('"')))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
}) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
warn!("No Authorization header");
|
||||
@@ -187,12 +196,10 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let json_body = serde_json::from_slice::<CanonicalJsonValue>(&body);
|
||||
|
||||
let mut request_map = BTreeMap::<String, CanonicalJsonValue>::new();
|
||||
|
||||
if let Ok(json_body) = json_body {
|
||||
request_map.insert("content".to_owned(), json_body);
|
||||
if let Some(json_body) = &json_body {
|
||||
request_map.insert("content".to_owned(), json_body.clone());
|
||||
};
|
||||
|
||||
request_map.insert(
|
||||
@@ -227,27 +234,24 @@ where
|
||||
CanonicalJsonValue::Object(signatures),
|
||||
);
|
||||
|
||||
let keys = match server_server::fetch_signing_keys(
|
||||
&db,
|
||||
&origin,
|
||||
vec![&key.to_owned()],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch signing keys: {}", e);
|
||||
let keys =
|
||||
match server_server::fetch_signing_keys(&db, &origin, vec![key.to_owned()])
|
||||
.await
|
||||
{
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch signing keys: {}", e);
|
||||
|
||||
// Forbidden
|
||||
return Failure((Status::raw(580), ()));
|
||||
}
|
||||
};
|
||||
// Forbidden
|
||||
return Failure((Status::raw(580), ()));
|
||||
}
|
||||
};
|
||||
|
||||
let mut pub_key_map = BTreeMap::new();
|
||||
pub_key_map.insert(origin.as_str().to_owned(), keys);
|
||||
|
||||
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
|
||||
Ok(()) => (None, None, false),
|
||||
Ok(()) => (None, None, Some(origin), false),
|
||||
Err(e) => {
|
||||
warn!("Failed to verify json request from {}: {}", origin, e);
|
||||
|
||||
@@ -260,7 +264,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthScheme::None => (None, None, false),
|
||||
AuthScheme::None => (None, None, None, false),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -271,6 +275,35 @@ where
|
||||
http_request = http_request.header(header.name.as_str(), &*header.value);
|
||||
}
|
||||
|
||||
if let Some(json_body) = json_body.as_mut().and_then(|val| val.as_object_mut()) {
|
||||
let user_id = sender_user.clone().unwrap_or_else(|| {
|
||||
UserId::parse_with_server_name("", db.globals.server_name())
|
||||
.expect("we know this is valid")
|
||||
});
|
||||
|
||||
if let Some(CanonicalJsonValue::Object(initial_request)) = json_body
|
||||
.get("auth")
|
||||
.and_then(|auth| auth.as_object())
|
||||
.and_then(|auth| auth.get("session"))
|
||||
.and_then(|session| session.as_str())
|
||||
.and_then(|session| {
|
||||
db.uiaa
|
||||
.get_uiaa_request(
|
||||
&user_id,
|
||||
&sender_device.clone().unwrap_or_else(|| "".into()),
|
||||
session,
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
{
|
||||
for (key, value) in initial_request {
|
||||
json_body.entry(key).or_insert(value);
|
||||
}
|
||||
}
|
||||
body = serde_json::to_vec(json_body).expect("value to bytes can't fail");
|
||||
}
|
||||
|
||||
let http_request = http_request.body(&*body).unwrap();
|
||||
debug!("{:?}", http_request);
|
||||
match <T::Incoming as IncomingRequest>::try_from_http_request(http_request) {
|
||||
@@ -278,14 +311,13 @@ where
|
||||
body: t,
|
||||
sender_user,
|
||||
sender_device,
|
||||
// TODO: Can we avoid parsing it again? (We only need this for append_pdu)
|
||||
json_body: utils::string_from_bytes(&body)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::value::RawValue::from_string(s).ok()),
|
||||
sender_servername,
|
||||
from_appservice,
|
||||
json_body,
|
||||
}),
|
||||
Err(e) => {
|
||||
warn!("{:?}", e);
|
||||
// Bad Json
|
||||
Failure((Status::raw(583), ()))
|
||||
}
|
||||
}
|
||||
@@ -303,49 +335,60 @@ impl<T: Outgoing> Deref for Ruma<T> {
|
||||
/// This struct converts ruma responses into rocket http responses.
|
||||
pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>;
|
||||
|
||||
pub struct RumaResponse<T: OutgoingResponse>(pub T);
|
||||
pub fn response<T: OutgoingResponse>(response: RumaResponse<T>) -> response::Result<'static> {
|
||||
let http_response = response
|
||||
.0
|
||||
.try_into_http_response::<Vec<u8>>()
|
||||
.map_err(|_| Status::InternalServerError)?;
|
||||
|
||||
impl<T: OutgoingResponse> From<T> for RumaResponse<T> {
|
||||
let mut response = rocket::response::Response::build();
|
||||
|
||||
let status = http_response.status();
|
||||
response.raw_status(status.into(), "");
|
||||
|
||||
for header in http_response.headers() {
|
||||
response.raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
|
||||
}
|
||||
|
||||
let http_body = http_response.into_body();
|
||||
|
||||
response.sized_body(http_body.len(), Cursor::new(http_body));
|
||||
|
||||
response.raw_header("Access-Control-Allow-Origin", "*");
|
||||
response.raw_header(
|
||||
"Access-Control-Allow-Methods",
|
||||
"GET, POST, PUT, DELETE, OPTIONS",
|
||||
);
|
||||
response.raw_header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
||||
);
|
||||
response.raw_header("Access-Control-Max-Age", "86400");
|
||||
response.ok()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RumaResponse<T>(pub T);
|
||||
|
||||
impl<T> From<T> for RumaResponse<T> {
|
||||
fn from(t: T) -> Self {
|
||||
Self(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RumaResponse<UiaaResponse> {
|
||||
fn from(t: Error) -> Self {
|
||||
t.to_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T>
|
||||
where
|
||||
T: Send + OutgoingResponse,
|
||||
'o: 'r,
|
||||
T: OutgoingResponse,
|
||||
{
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
|
||||
let http_response = self
|
||||
.0
|
||||
.try_into_http_response::<Vec<u8>>()
|
||||
.map_err(|_| Status::InternalServerError)?;
|
||||
|
||||
let mut response = rocket::response::Response::build();
|
||||
|
||||
let status = http_response.status();
|
||||
response.raw_status(status.into(), "");
|
||||
|
||||
for header in http_response.headers() {
|
||||
response.raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
|
||||
}
|
||||
|
||||
let http_body = http_response.into_body();
|
||||
|
||||
response.sized_body(http_body.len(), Cursor::new(http_body));
|
||||
|
||||
response.raw_header("Access-Control-Allow-Origin", "*");
|
||||
response.raw_header(
|
||||
"Access-Control-Allow-Methods",
|
||||
"GET, POST, PUT, DELETE, OPTIONS",
|
||||
);
|
||||
response.raw_header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
||||
);
|
||||
response.raw_header("Access-Control-Max-Age", "86400");
|
||||
response.ok()
|
||||
response(self)
|
||||
}
|
||||
}
|
||||
|
||||
+750
-384
File diff suppressed because it is too large
Load Diff
+55
-24
@@ -5,6 +5,7 @@ use ruma::serde::{try_from_json_map, CanonicalJsonError, CanonicalJsonObject};
|
||||
use std::{
|
||||
cmp,
|
||||
convert::TryInto,
|
||||
str::FromStr,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
@@ -15,6 +16,15 @@ pub fn millis_since_unix_epoch() -> u64 {
|
||||
.as_millis() as u64
|
||||
}
|
||||
|
||||
#[cfg(feature = "rocksdb")]
|
||||
pub fn increment_rocksdb(
|
||||
_new_key: &[u8],
|
||||
old: Option<&[u8]>,
|
||||
_operands: &mut rocksdb::MergeOperands,
|
||||
) -> Option<Vec<u8>> {
|
||||
increment(old)
|
||||
}
|
||||
|
||||
pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
|
||||
let number = match old.map(|bytes| bytes.try_into()) {
|
||||
Some(Ok(bytes)) => {
|
||||
@@ -27,16 +37,14 @@ pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
|
||||
Some(number.to_be_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn generate_keypair(old: Option<&[u8]>) -> Option<Vec<u8>> {
|
||||
Some(old.map(|s| s.to_vec()).unwrap_or_else(|| {
|
||||
let mut value = random_string(8).as_bytes().to_vec();
|
||||
value.push(0xff);
|
||||
value.extend_from_slice(
|
||||
&ruma::signatures::Ed25519KeyPair::generate()
|
||||
.expect("Ed25519KeyPair generation always works (?)"),
|
||||
);
|
||||
value
|
||||
}))
|
||||
pub fn generate_keypair() -> Vec<u8> {
|
||||
let mut value = random_string(8).as_bytes().to_vec();
|
||||
value.push(0xff);
|
||||
value.extend_from_slice(
|
||||
&ruma::signatures::Ed25519KeyPair::generate()
|
||||
.expect("Ed25519KeyPair generation always works (?)"),
|
||||
);
|
||||
value
|
||||
}
|
||||
|
||||
/// Parses the bytes into an u64.
|
||||
@@ -77,22 +85,19 @@ pub fn common_elements(
|
||||
let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>();
|
||||
|
||||
Some(first_iterator.filter(move |target| {
|
||||
other_iterators
|
||||
.iter_mut()
|
||||
.map(|it| {
|
||||
while let Some(element) = it.peek() {
|
||||
match check_order(element, target) {
|
||||
Ordering::Greater => return false, // We went too far
|
||||
Ordering::Equal => return true, // Element is in both iters
|
||||
Ordering::Less => {
|
||||
// Keep searching
|
||||
it.next();
|
||||
}
|
||||
other_iterators.iter_mut().all(|it| {
|
||||
while let Some(element) = it.peek() {
|
||||
match check_order(element, target) {
|
||||
Ordering::Greater => return false, // We went too far
|
||||
Ordering::Equal => return true, // Element is in both iters
|
||||
Ordering::Less => {
|
||||
// Keep searching
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
.all(|b| b)
|
||||
}
|
||||
false
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -111,3 +116,29 @@ pub fn to_canonical_object<T: serde::Serialize>(
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_from_str<
|
||||
'de,
|
||||
D: serde::de::Deserializer<'de>,
|
||||
T: FromStr<Err = E>,
|
||||
E: std::fmt::Display,
|
||||
>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<T, D::Error> {
|
||||
struct Visitor<T: FromStr<Err = E>, E>(std::marker::PhantomData<T>);
|
||||
impl<'de, T: FromStr<Err = Err>, Err: std::fmt::Display> serde::de::Visitor<'de>
|
||||
for Visitor<T, Err>
|
||||
{
|
||||
type Value = T;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(formatter, "a parsable string")
|
||||
}
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
v.parse().map_err(|e| serde::de::Error::custom(e))
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(Visitor(std::marker::PhantomData))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user