Compare commits
264 commits
recoverbro
...
next
Author | SHA1 | Date | |
---|---|---|---|
|
99ab234f40 | ||
|
e83416bb5a | ||
|
726b6f0fa6 | ||
|
d7fd89df49 | ||
|
f4e57fdb22 | ||
|
4f096adcfa | ||
21a5fa3ef0 | |||
b27e9ea95c | |||
8aa915acb9 | |||
ace9637bc2 | |||
|
be1e2e9307 | ||
|
1c6a4b1b24 | ||
976a73a0e5 | |||
4c06f329c4 | |||
d841b81c56 | |||
e707084345 | |||
|
6dcc8b6cf1 | ||
|
a2ac491c54 | ||
|
72a13d8353 | ||
|
3a63f9dfb6 | ||
|
f4f2d05b5b | ||
|
c3c7bcb2ed | ||
|
d6c57f9b2e | ||
|
7fb9e99649 | ||
|
1274b48ebb | ||
|
0a281e81a5 | ||
|
a43bde69fa | ||
|
986343877c | ||
|
2d47710b55 | ||
|
10542a1d70 | ||
|
c167f7a6ad | ||
|
5787a70bab | ||
|
cf8f1f2546 | ||
|
3c2fc4a4c6 | ||
|
dffd771e7c | ||
|
4da8c7e282 | ||
|
0df5d18fd6 | ||
|
825ceac1c3 | ||
|
3e389256f5 | ||
|
a7892a28ec | ||
|
9453dbc740 | ||
|
bf48c10d28 | ||
|
7c1a3e41d9 | ||
|
2a04a361e0 | ||
|
0e8e4f1083 | ||
|
81ae579b25 | ||
|
3a3cafe912 | ||
|
d29591d47d | ||
|
67d280dd2e | ||
|
3ac9be5a78 | ||
|
52954f7a11 | ||
|
692a31620d | ||
|
cf4015b830 | ||
|
9cef03127b | ||
|
249fc7769d | ||
|
5cc53c9e14 | ||
|
bdc46f6392 | ||
|
6ae776218c | ||
|
bd2b146d5d | ||
|
f7cc4fb3bb | ||
|
ca198c51fa | ||
|
fe86d28428 | ||
|
c86f9a5c5b | ||
|
e0358a9de5 | ||
|
69d0003222 | ||
|
5cf9f3df48 | ||
|
0b7ed5adc9 | ||
|
4de54db305 | ||
|
02781e4f9b | ||
|
f8bdfd82b0 | ||
|
7e66d2e2c0 | ||
|
ffd03a256b | ||
|
9d592d60d2 | ||
|
25ceb5ebd8 | ||
|
6f052fff98 | ||
|
e8ac881b2f | ||
|
0d17aedae5 | ||
|
ab1fff2642 | ||
|
92c5b6b86c | ||
|
dc2f53e773 | ||
|
2475995102 | ||
|
835f4ad8cf | ||
|
ca6219723b | ||
|
40c7c248fb | ||
|
8f3f5c01f9 | ||
|
9d7f7b871b | ||
|
30f0871e21 | ||
|
98e81c6217 | ||
|
f3b6b3e222 | ||
|
3bfdae795d | ||
|
75c80df271 | ||
|
094cb888d4 | ||
|
fa725a14e2 | ||
|
9b3664aeeb | ||
|
90fea00dc7 | ||
|
20924a44f1 | ||
|
38d6426b0e | ||
|
9b55ce933a | ||
|
f73a657a23 | ||
|
6dfb262ddf | ||
|
75cdc3a1f6 | ||
|
11103a92ed | ||
|
ce2017a10e | ||
|
0c2cfda3ae | ||
|
4bf8ee1f74 | ||
|
5d16948030 | ||
|
b7b2eb9d05 | ||
|
19bfee1835 | ||
|
9db87550fd | ||
|
606b25b9e7 | ||
|
fd9e52a559 | ||
|
0a0f227601 | ||
|
183558150d | ||
|
c028e0553c | ||
|
2581f7a10b | ||
|
3e518773e2 | ||
|
888f7e4403 | ||
|
d82c26f0a9 | ||
|
c1e2ffc0cd | ||
|
06fccbc340 | ||
|
fbd8090b0b | ||
|
06ab707c79 | ||
|
174a580319 | ||
|
fbb256dd91 | ||
|
5a7bade476 | ||
|
d2bfcb018e | ||
|
08f0f17ff7 | ||
|
57b86f1130 | ||
|
3a6eee7019 | ||
|
9ce1cad983 | ||
|
10da9485a5 | ||
|
acfe381dd3 | ||
|
83805c66e5 | ||
|
afd8112e25 | ||
|
b8c164dc60 | ||
|
0453a72890 | ||
|
e2c914cc11 | ||
|
da907451e7 | ||
|
2b4a6c96ee | ||
|
d7061e6984 | ||
|
3494d7759e | ||
|
cc5dcceacc | ||
|
863103450c | ||
|
a0148a9996 | ||
|
1f867a2c86 | ||
|
c0a2acb869 | ||
|
97835541ce | ||
|
081cc66eda | ||
|
7489e2c4f6 | ||
|
1e675dbb68 | ||
|
f4c1748ab1 | ||
|
7990822f72 | ||
|
2a100412fa | ||
|
3e7652909b | ||
|
9fb8498067 | ||
|
291290db92 | ||
|
54a115caf3 | ||
|
81866170f0 | ||
|
bf46829595 | ||
|
9f14ad7125 | ||
|
90a10c84ef | ||
|
d220641d64 | ||
|
caddc656fb | ||
|
b1a591a06c | ||
|
3cd3d0e0ff | ||
|
433dad6ac2 | ||
|
8cf408e966 | ||
|
1e560529d8 | ||
|
ff98444d03 | ||
|
82f31d6b72 | ||
|
6ae5143ff5 | ||
|
bd8fec3836 | ||
|
742331e054 | ||
|
abd8e1bf54 | ||
|
fa3b1fd9bd | ||
|
e9946f81a0 | ||
|
a9ba067e77 | ||
|
706148f941 | ||
|
24402312c5 | ||
|
17180a3e08 | ||
|
3c6ffd88bf | ||
|
c3966f501c | ||
|
56f0f3dfa4 | ||
|
ad06d475de | ||
|
0b4e3de9c0 | ||
|
edd4a3733f | ||
|
c17187777f | ||
|
78e7b711df | ||
|
4b7d3e24dd | ||
|
e4f769963f | ||
|
eab5dac6e8 | ||
|
c4824a6ebc | ||
|
f8a36e7554 | ||
|
a2c3256ced | ||
|
833c1505f1 | ||
|
bac13d08ae | ||
|
f0a27dcb00 | ||
|
9d49d599f3 | ||
|
2640f67e4b | ||
|
eb8bc1af8d | ||
|
0ded637b4a | ||
|
dc50197a13 | ||
|
06a1321e56 | ||
|
6a6f8e80f1 | ||
|
fd1ccbd3ad | ||
|
3a1a72df98 | ||
|
84784970b2 | ||
|
d64a56d88b | ||
|
be877ef719 | ||
|
7c6d25dcd1 | ||
|
b671238aa0 | ||
|
91180e011d | ||
|
26b8605fa0 | ||
|
dbd360ebb9 | ||
|
48e6e0659f | ||
|
72eb1972c1 | ||
|
63cbaedb79 | ||
|
db6def8800 | ||
|
caa841c434 | ||
|
49a0f3a60d | ||
|
bac82f43af | ||
|
15cc801840 | ||
|
5f9ca8e458 | ||
|
c7e0ea525a | ||
|
abd0a014e8 | ||
|
4a7d3c7301 | ||
|
15e60818c9 | ||
|
def079267d | ||
|
a3a9b60abc | ||
|
808b12f618 | ||
|
faa9208a3e | ||
|
1ea27c4f97 | ||
|
422ee40107 | ||
|
0280fa5793 | ||
|
664d6baace | ||
|
be9196430d | ||
|
533bccad8f | ||
|
a4261aac76 | ||
|
c38df57279 | ||
|
4e2bbf9d6a | ||
|
7a9ec851fc | ||
|
d62cd2ae51 | ||
|
49b5af6d45 | ||
|
1f1444da8c | ||
|
2a9a908343 | ||
|
921b266d86 | ||
|
dbbd164e39 | ||
|
f5e3b0e2dd | ||
|
1b9e63f426 | ||
|
eb4323cc0f | ||
|
a6712627e4 | ||
|
3be32c4dac | ||
|
55149e3336 | ||
|
2b63e46fc5 | ||
|
a0c449e570 | ||
|
c997311bea | ||
|
1929ca5d9d | ||
|
88c6bf7595 | ||
|
4635644e21 | ||
|
f53ecaa97d | ||
|
f704169aeb | ||
|
2a7c4693b8 | ||
|
da3871f39a | ||
|
4d589d9788 |
106 changed files with 6710 additions and 3372 deletions
4
.envrc
4
.envrc
|
@ -1 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
use flake
|
use flake
|
||||||
|
|
||||||
|
PATH_add bin
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -68,3 +68,6 @@ cached_target
|
||||||
|
|
||||||
# Direnv cache
|
# Direnv cache
|
||||||
/.direnv
|
/.direnv
|
||||||
|
|
||||||
|
# Gitlab CI cache
|
||||||
|
/.gitlab-ci.d
|
||||||
|
|
397
.gitlab-ci.yml
397
.gitlab-ci.yml
|
@ -1,241 +1,180 @@
|
||||||
stages:
|
stages:
|
||||||
- build
|
- ci
|
||||||
- build docker image
|
- artifacts
|
||||||
- test
|
- publish
|
||||||
- upload artifacts
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# Make GitLab CI go fast:
|
# Makes some things print in color
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
TERM: ansi
|
||||||
FF_USE_FASTZIP: 1
|
|
||||||
CACHE_COMPRESSION_LEVEL: fastest
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------- #
|
before_script:
|
||||||
# Create and publish docker image #
|
# Enable nix-command and flakes
|
||||||
# --------------------------------------------------------------------- #
|
- if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
|
||||||
|
|
||||||
.docker-shared-settings:
|
# Add our own binary cache
|
||||||
stage: "build docker image"
|
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix.computer.surgery/conduit" >> /etc/nix/nix.conf; fi
|
||||||
image:
|
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=" >> /etc/nix/nix.conf; fi
|
||||||
name: jdrouet/docker-with-buildx:20.10.21-0.9.1
|
|
||||||
pull_policy: if-not-present
|
# Add crane binary cache
|
||||||
needs: []
|
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
|
||||||
tags: [ "docker" ]
|
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
|
||||||
variables:
|
|
||||||
# Docker in Docker:
|
# Add nix-community binary cache
|
||||||
DOCKER_HOST: tcp://docker:2375/
|
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
|
||||||
DOCKER_TLS_CERTDIR: ""
|
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
|
||||||
# Famedly runners use BTRFS, overlayfs and overlay2 often break jobs
|
|
||||||
DOCKER_DRIVER: btrfs
|
# Install direnv and nix-direnv
|
||||||
|
- if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi
|
||||||
|
|
||||||
|
# Allow .envrc
|
||||||
|
- if command -v nix > /dev/null; then direnv allow; fi
|
||||||
|
|
||||||
|
# Set CARGO_HOME to a cacheable path
|
||||||
|
- export CARGO_HOME="$(git rev-parse --show-toplevel)/.gitlab-ci.d/cargo"
|
||||||
|
|
||||||
|
ci:
|
||||||
|
stage: ci
|
||||||
|
image: nixos/nix:2.19.2
|
||||||
|
script:
|
||||||
|
- direnv exec . engage
|
||||||
|
cache:
|
||||||
|
key: nix
|
||||||
|
paths:
|
||||||
|
- target
|
||||||
|
- .gitlab-ci.d
|
||||||
|
|
||||||
|
static:x86_64-unknown-linux-musl:
|
||||||
|
stage: artifacts
|
||||||
|
image: nixos/nix:2.19.2
|
||||||
|
script:
|
||||||
|
# Push artifacts and build requirements to binary cache
|
||||||
|
- ./bin/nix-build-and-cache .#static-x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Make the output less difficult to find
|
||||||
|
- cp result/bin/conduit conduit
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- conduit
|
||||||
|
|
||||||
|
static:aarch64-unknown-linux-musl:
|
||||||
|
stage: artifacts
|
||||||
|
image: nixos/nix:2.19.2
|
||||||
|
script:
|
||||||
|
# Push artifacts and build requirements to binary cache
|
||||||
|
- ./bin/nix-build-and-cache .#static-aarch64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Make the output less difficult to find
|
||||||
|
- cp result/bin/conduit conduit
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- conduit
|
||||||
|
|
||||||
|
# Note that although we have an `oci-image-x86_64-unknown-linux-musl` output,
|
||||||
|
# we don't build it because it would be largely redundant to this one since it's
|
||||||
|
# all containerized anyway.
|
||||||
|
oci-image:x86_64-unknown-linux-gnu:
|
||||||
|
stage: artifacts
|
||||||
|
image: nixos/nix:2.19.2
|
||||||
|
script:
|
||||||
|
# Push artifacts and build requirements to binary cache
|
||||||
|
#
|
||||||
|
# Since the OCI image package is based on the binary package, this has the
|
||||||
|
# fun side effect of uploading the normal binary too. Conduit users who are
|
||||||
|
# deploying with Nix can leverage this fact by adding our binary cache to
|
||||||
|
# their systems.
|
||||||
|
- ./bin/nix-build-and-cache .#oci-image
|
||||||
|
|
||||||
|
# Make the output less difficult to find
|
||||||
|
- cp result oci-image-amd64.tar.gz
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- oci-image-amd64.tar.gz
|
||||||
|
|
||||||
|
oci-image:aarch64-unknown-linux-musl:
|
||||||
|
stage: artifacts
|
||||||
|
needs:
|
||||||
|
# Wait for the static binary job to finish before starting so we don't have
|
||||||
|
# to build that twice for no reason
|
||||||
|
- static:aarch64-unknown-linux-musl
|
||||||
|
image: nixos/nix:2.19.2
|
||||||
|
script:
|
||||||
|
# Push artifacts and build requirements to binary cache
|
||||||
|
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Make the output less difficult to find
|
||||||
|
- cp result oci-image-arm64v8.tar.gz
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- oci-image-arm64v8.tar.gz
|
||||||
|
|
||||||
|
debian:x86_64-unknown-linux-gnu:
|
||||||
|
stage: artifacts
|
||||||
|
# See also `rust-toolchain.toml`
|
||||||
|
image: rust:1.75.0
|
||||||
|
script:
|
||||||
|
- apt-get update && apt-get install -y --no-install-recommends libclang-dev
|
||||||
|
- cargo install cargo-deb
|
||||||
|
- cargo deb
|
||||||
|
|
||||||
|
# Make the output less difficult to find
|
||||||
|
- mv target/debian/*.deb conduit.deb
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- conduit.deb
|
||||||
|
cache:
|
||||||
|
key: debian
|
||||||
|
paths:
|
||||||
|
- target
|
||||||
|
- .gitlab-ci.d
|
||||||
|
|
||||||
|
.push-oci-image:
|
||||||
|
stage: publish
|
||||||
|
image: docker:25.0.0
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:25.0.0-dind
|
||||||
script:
|
|
||||||
- apk add openssh-client
|
|
||||||
- eval $(ssh-agent -s)
|
|
||||||
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
|
||||||
- printf "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config
|
|
||||||
- sh .gitlab/setup-buildx-remote-builders.sh
|
|
||||||
# Authorize against this project's own image registry:
|
|
||||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
|
||||||
# Build multiplatform image and push to temporary tag:
|
|
||||||
- >
|
|
||||||
docker buildx build
|
|
||||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
|
||||||
--pull
|
|
||||||
--tag "$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
|
||||||
--push
|
|
||||||
--file "Dockerfile" .
|
|
||||||
# Build multiplatform image to deb stage and extract their .deb files:
|
|
||||||
- >
|
|
||||||
docker buildx build
|
|
||||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
|
||||||
--target "packager-result"
|
|
||||||
--output="type=local,dest=/tmp/build-output"
|
|
||||||
--file "Dockerfile" .
|
|
||||||
# Build multiplatform image to binary stage and extract their binaries:
|
|
||||||
- >
|
|
||||||
docker buildx build
|
|
||||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
|
||||||
--target "builder-result"
|
|
||||||
--output="type=local,dest=/tmp/build-output"
|
|
||||||
--file "Dockerfile" .
|
|
||||||
# Copy to GitLab container registry:
|
|
||||||
- >
|
|
||||||
docker buildx imagetools create
|
|
||||||
--tag "$CI_REGISTRY_IMAGE/$TAG"
|
|
||||||
--tag "$CI_REGISTRY_IMAGE/$TAG-bullseye"
|
|
||||||
--tag "$CI_REGISTRY_IMAGE/$TAG-commit-$CI_COMMIT_SHORT_SHA"
|
|
||||||
"$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
|
||||||
# if DockerHub credentials exist, also copy to dockerhub:
|
|
||||||
- if [ -n "${DOCKER_HUB}" ]; then docker login -u "$DOCKER_HUB_USER" -p "$DOCKER_HUB_PASSWORD" "$DOCKER_HUB"; fi
|
|
||||||
- >
|
|
||||||
if [ -n "${DOCKER_HUB}" ]; then
|
|
||||||
docker buildx imagetools create
|
|
||||||
--tag "$DOCKER_HUB_IMAGE/$TAG"
|
|
||||||
--tag "$DOCKER_HUB_IMAGE/$TAG-bullseye"
|
|
||||||
--tag "$DOCKER_HUB_IMAGE/$TAG-commit-$CI_COMMIT_SHORT_SHA"
|
|
||||||
"$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
|
||||||
; fi
|
|
||||||
- mv /tmp/build-output ./
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- "./build-output/"
|
|
||||||
|
|
||||||
docker:next:
|
|
||||||
extends: .docker-shared-settings
|
|
||||||
rules:
|
|
||||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "next"'
|
|
||||||
variables:
|
variables:
|
||||||
TAG: "matrix-conduit:next"
|
IMAGE_SUFFIX_AMD64: amd64
|
||||||
|
IMAGE_SUFFIX_ARM64V8: arm64v8
|
||||||
|
script:
|
||||||
|
- docker load -i oci-image-amd64.tar.gz
|
||||||
|
- IMAGE_ID_AMD64=$(docker images -q conduit:next)
|
||||||
|
- docker load -i oci-image-arm64v8.tar.gz
|
||||||
|
- IMAGE_ID_ARM64V8=$(docker images -q conduit:next)
|
||||||
|
# Tag and push the architecture specific images
|
||||||
|
- docker tag $IMAGE_ID_AMD64 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
|
||||||
|
- docker tag $IMAGE_ID_ARM64V8 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||||
|
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
|
||||||
|
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||||
|
# Tag the multi-arch image
|
||||||
|
- docker manifest create $IMAGE_NAME:$CI_COMMIT_SHA --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||||
|
- docker manifest push $IMAGE_NAME:$CI_COMMIT_SHA
|
||||||
|
# Tag and push the git ref
|
||||||
|
- docker manifest create $IMAGE_NAME:$CI_COMMIT_REF_NAME --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||||
|
- docker manifest push $IMAGE_NAME:$CI_COMMIT_REF_NAME
|
||||||
|
# Tag git tags as 'latest'
|
||||||
|
- |
|
||||||
|
if [[ -n "$CI_COMMIT_TAG" ]]; then
|
||||||
|
docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||||
|
docker manifest push $IMAGE_NAME:latest
|
||||||
|
fi
|
||||||
|
dependencies:
|
||||||
|
- oci-image:x86_64-unknown-linux-gnu
|
||||||
|
- oci-image:aarch64-unknown-linux-musl
|
||||||
|
only:
|
||||||
|
- next
|
||||||
|
- master
|
||||||
|
- tags
|
||||||
|
|
||||||
docker:master:
|
oci-image:push-gitlab:
|
||||||
extends: .docker-shared-settings
|
extends: .push-oci-image
|
||||||
rules:
|
|
||||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "master"'
|
|
||||||
variables:
|
variables:
|
||||||
TAG: "matrix-conduit:latest"
|
IMAGE_NAME: $CI_REGISTRY_IMAGE/matrix-conduit
|
||||||
|
before_script:
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
|
||||||
docker:tags:
|
oci-image:push-dockerhub:
|
||||||
extends: .docker-shared-settings
|
extends: .push-oci-image
|
||||||
rules:
|
|
||||||
- if: "$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_TAG"
|
|
||||||
variables:
|
variables:
|
||||||
TAG: "matrix-conduit:$CI_COMMIT_TAG"
|
IMAGE_NAME: matrixconduit/matrix-conduit
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
# Run tests #
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
cargo check:
|
|
||||||
stage: test
|
|
||||||
image: docker.io/rust:1.64.0-bullseye
|
|
||||||
needs: []
|
|
||||||
interruptible: true
|
|
||||||
before_script:
|
before_script:
|
||||||
- "rustup show && rustc --version && cargo --version" # Print version info for debugging
|
- docker login -u $DOCKER_HUB_USER -p $DOCKER_HUB_PASSWORD
|
||||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
|
||||||
script:
|
|
||||||
- cargo check
|
|
||||||
|
|
||||||
|
|
||||||
.test-shared-settings:
|
|
||||||
stage: "test"
|
|
||||||
needs: []
|
|
||||||
image: "registry.gitlab.com/jfowl/conduit-containers/rust-with-tools:latest"
|
|
||||||
tags: ["docker"]
|
|
||||||
variables:
|
|
||||||
CARGO_INCREMENTAL: "false" # https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow
|
|
||||||
interruptible: true
|
|
||||||
|
|
||||||
test:cargo:
|
|
||||||
extends: .test-shared-settings
|
|
||||||
before_script:
|
|
||||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
|
||||||
script:
|
|
||||||
- rustc --version && cargo --version # Print version info for debugging
|
|
||||||
- "cargo test --color always --workspace --verbose --locked --no-fail-fast -- -Z unstable-options --format json | gitlab-report -p test > $CI_PROJECT_DIR/report.xml"
|
|
||||||
artifacts:
|
|
||||||
when: always
|
|
||||||
reports:
|
|
||||||
junit: report.xml
|
|
||||||
|
|
||||||
test:clippy:
|
|
||||||
extends: .test-shared-settings
|
|
||||||
allow_failure: true
|
|
||||||
before_script:
|
|
||||||
- rustup component add clippy
|
|
||||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
|
||||||
script:
|
|
||||||
- rustc --version && cargo --version # Print version info for debugging
|
|
||||||
- "cargo clippy --color always --verbose --message-format=json | gitlab-report -p clippy > $CI_PROJECT_DIR/gl-code-quality-report.json"
|
|
||||||
artifacts:
|
|
||||||
when: always
|
|
||||||
reports:
|
|
||||||
codequality: gl-code-quality-report.json
|
|
||||||
|
|
||||||
test:format:
|
|
||||||
extends: .test-shared-settings
|
|
||||||
before_script:
|
|
||||||
- rustup component add rustfmt
|
|
||||||
script:
|
|
||||||
- cargo fmt --all -- --check
|
|
||||||
|
|
||||||
test:audit:
|
|
||||||
extends: .test-shared-settings
|
|
||||||
allow_failure: true
|
|
||||||
script:
|
|
||||||
- cargo audit --color always || true
|
|
||||||
- cargo audit --stale --json | gitlab-report -p audit > gl-sast-report.json
|
|
||||||
artifacts:
|
|
||||||
when: always
|
|
||||||
reports:
|
|
||||||
sast: gl-sast-report.json
|
|
||||||
|
|
||||||
test:dockerlint:
|
|
||||||
stage: "test"
|
|
||||||
needs: []
|
|
||||||
image: "ghcr.io/hadolint/hadolint@sha256:6c4b7c23f96339489dd35f21a711996d7ce63047467a9a562287748a03ad5242" # 2.8.0-alpine
|
|
||||||
interruptible: true
|
|
||||||
script:
|
|
||||||
- hadolint --version
|
|
||||||
# First pass: Print for CI log:
|
|
||||||
- >
|
|
||||||
hadolint
|
|
||||||
--no-fail --verbose
|
|
||||||
./Dockerfile
|
|
||||||
# Then output the results into a json for GitLab to pretty-print this in the MR:
|
|
||||||
- >
|
|
||||||
hadolint
|
|
||||||
--format gitlab_codeclimate
|
|
||||||
--failure-threshold error
|
|
||||||
./Dockerfile > dockerlint.json
|
|
||||||
artifacts:
|
|
||||||
when: always
|
|
||||||
reports:
|
|
||||||
codequality: dockerlint.json
|
|
||||||
paths:
|
|
||||||
- dockerlint.json
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_REF_NAME != "master"'
|
|
||||||
changes:
|
|
||||||
- docker/*Dockerfile
|
|
||||||
- Dockerfile
|
|
||||||
- .gitlab-ci.yml
|
|
||||||
- if: '$CI_COMMIT_REF_NAME == "master"'
|
|
||||||
- if: '$CI_COMMIT_REF_NAME == "next"'
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
# Store binaries as package so they have download urls #
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
# DISABLED FOR NOW, NEEDS TO BE FIXED AT A LATER TIME:
|
|
||||||
|
|
||||||
#publish:package:
|
|
||||||
# stage: "upload artifacts"
|
|
||||||
# needs:
|
|
||||||
# - "docker:tags"
|
|
||||||
# rules:
|
|
||||||
# - if: "$CI_COMMIT_TAG"
|
|
||||||
# image: curlimages/curl:latest
|
|
||||||
# tags: ["docker"]
|
|
||||||
# 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 build-output/linux_amd64/conduit "${BASE_URL}/conduit-x86_64-unknown-linux-gnu"'
|
|
||||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm_v7/conduit "${BASE_URL}/conduit-armv7-unknown-linux-gnu"'
|
|
||||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm64/conduit "${BASE_URL}/conduit-aarch64-unknown-linux-gnu"'
|
|
||||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_amd64/conduit.deb "${BASE_URL}/conduit-x86_64-unknown-linux-gnu.deb"'
|
|
||||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm_v7/conduit.deb "${BASE_URL}/conduit-armv7-unknown-linux-gnu.deb"'
|
|
||||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm64/conduit.deb "${BASE_URL}/conduit-aarch64-unknown-linux-gnu.deb"'
|
|
||||||
|
|
||||||
# Avoid duplicate pipelines
|
|
||||||
# See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
||||||
- if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
|
|
||||||
when: never
|
|
||||||
- if: "$CI_COMMIT_BRANCH"
|
|
||||||
- if: "$CI_COMMIT_TAG"
|
|
2006
Cargo.lock
generated
2006
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
108
Cargo.toml
108
Cargo.toml
|
@ -1,3 +1,14 @@
|
||||||
|
# Keep alphabetically sorted
|
||||||
|
[workspace.lints.rust]
|
||||||
|
explicit_outlives_requirements = "warn"
|
||||||
|
unused_qualifications = "warn"
|
||||||
|
|
||||||
|
# Keep alphabetically sorted
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
cloned_instead_of_copied = "warn"
|
||||||
|
dbg_macro = "warn"
|
||||||
|
str_to_string = "warn"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "conduit"
|
name = "conduit"
|
||||||
description = "A Matrix homeserver written in Rust"
|
description = "A Matrix homeserver written in Rust"
|
||||||
|
@ -6,107 +17,114 @@ authors = ["timokoesters <timo@koesters.xyz>"]
|
||||||
homepage = "https://conduit.rs"
|
homepage = "https://conduit.rs"
|
||||||
repository = "https://gitlab.com/famedly/conduit"
|
repository = "https://gitlab.com/famedly/conduit"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "0.6.0-alpha"
|
version = "0.7.0-alpha"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# When changing this, make sure to update the `flake.lock` file by running
|
# See also `rust-toolchain.toml`
|
||||||
# `nix flake update`. If you don't have Nix installed or otherwise don't know
|
rust-version = "1.75.0"
|
||||||
# how to do this, ping `@charles:computer.surgery` or `@dusk:gaze.systems` in
|
|
||||||
# the matrix room.
|
|
||||||
rust-version = "1.64.0"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Web framework
|
# Web framework
|
||||||
axum = { version = "0.5.17", default-features = false, features = ["form", "headers", "http1", "http2", "json", "matched-path"], optional = true }
|
axum = { version = "0.6.18", default-features = false, features = ["form", "headers", "http1", "http2", "json", "matched-path"], optional = true }
|
||||||
axum-server = { version = "0.4.0", features = ["tls-rustls"] }
|
axum-server = { version = "0.5.1", features = ["tls-rustls"] }
|
||||||
tower = { version = "0.4.8", features = ["util"] }
|
tower = { version = "0.4.13", features = ["util"] }
|
||||||
tower-http = { version = "0.3.4", features = ["add-extension", "cors", "compression-full", "sensitive-headers", "trace", "util"] }
|
tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitive-headers", "trace", "util"] }
|
||||||
|
|
||||||
# Used for matrix spec type definitions and helpers
|
# Used for matrix spec type definitions and helpers
|
||||||
#ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
#ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||||
ruma = { git = "https://github.com/ruma/ruma", rev = "67d0f3cc04a8d1dc4a8a1ec947519967ce11ce26", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
ruma = { git = "https://github.com/ruma/ruma", rev = "1a1c61ee1e8f0936e956a3b69c931ce12ee28475", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||||
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "50c1db7e0a3a21fc794b0cce3b64285a4c750c71", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||||
#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"] }
|
#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||||
|
|
||||||
# Async runtime and utilities
|
# Async runtime and utilities
|
||||||
tokio = { version = "1.11.0", features = ["fs", "macros", "signal", "sync"] }
|
tokio = { version = "1.28.1", features = ["fs", "macros", "signal", "sync"] }
|
||||||
# Used for storing data permanently
|
# Used for storing data permanently
|
||||||
#sled = { version = "0.34.7", features = ["compression", "no_metrics"], optional = true }
|
#sled = { version = "0.34.7", features = ["compression", "no_metrics"], optional = true }
|
||||||
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
|
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
|
||||||
persy = { version = "1.0.0", optional = true, features = ["background_ops"] }
|
persy = { version = "1.4.4", optional = true, features = ["background_ops"] }
|
||||||
|
|
||||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||||
bytes = "1.1.0"
|
bytes = "1.4.0"
|
||||||
http = "0.2.4"
|
http = "0.2.9"
|
||||||
# Used to find data directory for default db path
|
# Used to find data directory for default db path
|
||||||
directories = "4.0.0"
|
directories = "4.0.1"
|
||||||
# Used for ruma wrapper
|
# Used for ruma wrapper
|
||||||
serde_json = { version = "1.0.68", features = ["raw_value"] }
|
serde_json = { version = "1.0.96", features = ["raw_value"] }
|
||||||
# Used for appservice registration files
|
# Used for appservice registration files
|
||||||
serde_yaml = "0.9.13"
|
serde_yaml = "0.9.21"
|
||||||
# Used for pdu definition
|
# Used for pdu definition
|
||||||
serde = { version = "1.0.130", features = ["rc"] }
|
serde = { version = "1.0.163", features = ["rc"] }
|
||||||
# Used for secure identifiers
|
# Used for secure identifiers
|
||||||
rand = "0.8.4"
|
rand = "0.8.5"
|
||||||
# Used to hash passwords
|
# Used to hash passwords
|
||||||
rust-argon2 = "1.0.0"
|
rust-argon2 = "1.0.0"
|
||||||
# Used to send requests
|
# Used to send requests
|
||||||
reqwest = { default-features = false, features = ["rustls-tls-native-roots", "socks"], git = "https://github.com/timokoesters/reqwest", rev = "57b7cf4feb921573dfafad7d34b9ac6e44ead0bd" }
|
hyper = "0.14.26"
|
||||||
|
reqwest = { version = "0.11.18", default-features = false, features = ["rustls-tls-native-roots", "socks"] }
|
||||||
# Used for conduit::Error type
|
# Used for conduit::Error type
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.40"
|
||||||
# Used to generate thumbnails for images
|
# Used to generate thumbnails for images
|
||||||
image = { version = "0.24.4", default-features = false, features = ["jpeg", "png", "gif"] }
|
image = { version = "0.24.6", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||||
# Used to encode server public key
|
# Used to encode server public key
|
||||||
base64 = "0.13.0"
|
base64 = "0.21.2"
|
||||||
# Used when hashing the state
|
# Used when hashing the state
|
||||||
ring = "0.16.20"
|
ring = "0.17.7"
|
||||||
# Used when querying the SRV record of other servers
|
# Used when querying the SRV record of other servers
|
||||||
trust-dns-resolver = "0.22.0"
|
trust-dns-resolver = "0.22.0"
|
||||||
# Used to find matching events for appservices
|
# Used to find matching events for appservices
|
||||||
regex = "1.5.4"
|
regex = "1.8.1"
|
||||||
# jwt jsonwebtokens
|
# jwt jsonwebtokens
|
||||||
jsonwebtoken = "8.1.1"
|
jsonwebtoken = "9.2.0"
|
||||||
# Performance measurements
|
# Performance measurements
|
||||||
tracing = { version = "0.1.27", features = [] }
|
tracing = { version = "0.1.37", features = [] }
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
tracing-flame = "0.2.0"
|
tracing-flame = "0.2.0"
|
||||||
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
|
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
|
||||||
opentelemetry-jaeger = { version = "0.17.0", features = ["rt-tokio"] }
|
opentelemetry-jaeger = { version = "0.17.0", features = ["rt-tokio"] }
|
||||||
tracing-opentelemetry = "0.18.0"
|
tracing-opentelemetry = "0.18.0"
|
||||||
lru-cache = "0.1.2"
|
lru-cache = "0.1.2"
|
||||||
rusqlite = { version = "0.28.0", optional = true, features = ["bundled"] }
|
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }
|
||||||
parking_lot = { version = "0.12.1", optional = true }
|
parking_lot = { version = "0.12.1", optional = true }
|
||||||
crossbeam = { version = "0.8.1", optional = true }
|
# crossbeam = { version = "0.8.2", optional = true }
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.15.0"
|
||||||
threadpool = "1.8.1"
|
threadpool = "1.8.1"
|
||||||
heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
|
# heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
|
||||||
rocksdb = { version = "0.17.0", default-features = true, features = ["multi-threaded-cf", "zstd"], optional = true }
|
# Used for ruma wrapper
|
||||||
|
serde_html_form = "0.2.0"
|
||||||
|
|
||||||
thread_local = "1.1.3"
|
rocksdb = { version = "0.21.0", default-features = true, features = ["multi-threaded-cf", "zstd"], optional = true }
|
||||||
|
|
||||||
|
thread_local = "1.1.7"
|
||||||
# used for TURN server authentication
|
# used for TURN server authentication
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
sha-1 = "0.10.0"
|
sha-1 = "0.10.1"
|
||||||
# used for conduit's CLI and admin room command parsing
|
# used for conduit's CLI and admin room command parsing
|
||||||
clap = { version = "4.0.11", default-features = false, features = ["std", "derive", "help", "usage", "error-context"] }
|
clap = { version = "4.3.0", default-features = false, features = ["std", "derive", "help", "usage", "error-context"] }
|
||||||
futures-util = { version = "0.3.17", default-features = false }
|
futures-util = { version = "0.3.28", default-features = false }
|
||||||
# Used for reading the configuration from conduit.toml & environment variables
|
# Used for reading the configuration from conduit.toml & environment variables
|
||||||
figment = { version = "0.10.6", features = ["env", "toml"] }
|
figment = { version = "0.10.8", features = ["env", "toml"] }
|
||||||
|
|
||||||
tikv-jemalloc-ctl = { version = "0.5.0", features = ["use_std"], optional = true }
|
tikv-jemalloc-ctl = { version = "0.5.0", features = ["use_std"], optional = true }
|
||||||
tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"], optional = true }
|
tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"], optional = true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.68"
|
||||||
|
|
||||||
sd-notify = { version = "0.4.1", optional = true }
|
sd-notify = { version = "0.4.1", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
nix = { version = "0.26.2", features = ["resource"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "jemalloc", "systemd"]
|
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "systemd"]
|
||||||
#backend_sled = ["sled"]
|
#backend_sled = ["sled"]
|
||||||
backend_persy = ["persy", "parking_lot"]
|
backend_persy = ["persy", "parking_lot"]
|
||||||
backend_sqlite = ["sqlite"]
|
backend_sqlite = ["sqlite"]
|
||||||
backend_heed = ["heed", "crossbeam"]
|
#backend_heed = ["heed", "crossbeam"]
|
||||||
backend_rocksdb = ["rocksdb"]
|
backend_rocksdb = ["rocksdb"]
|
||||||
jemalloc = ["tikv-jemalloc-ctl", "tikv-jemallocator"]
|
jemalloc = ["tikv-jemalloc-ctl", "tikv-jemallocator"]
|
||||||
sqlite = ["rusqlite", "parking_lot", "tokio/signal"]
|
sqlite = ["rusqlite", "parking_lot", "tokio/signal"]
|
||||||
|
@ -134,7 +152,7 @@ instead of a server that has high scalability."""
|
||||||
section = "net"
|
section = "net"
|
||||||
priority = "optional"
|
priority = "optional"
|
||||||
assets = [
|
assets = [
|
||||||
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
|
["debian/README.md", "usr/share/doc/matrix-conduit/README.Debian", "644"],
|
||||||
["README.md", "usr/share/doc/matrix-conduit/", "644"],
|
["README.md", "usr/share/doc/matrix-conduit/", "644"],
|
||||||
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
|
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
|
||||||
]
|
]
|
||||||
|
|
23
Cross.toml
23
Cross.toml
|
@ -1,23 +0,0 @@
|
||||||
[build.env]
|
|
||||||
# CI uses an S3 endpoint to store sccache artifacts, so their config needs to
|
|
||||||
# be available in the cross container as well
|
|
||||||
passthrough = [
|
|
||||||
"RUSTC_WRAPPER",
|
|
||||||
"AWS_ACCESS_KEY_ID",
|
|
||||||
"AWS_SECRET_ACCESS_KEY",
|
|
||||||
"SCCACHE_BUCKET",
|
|
||||||
"SCCACHE_ENDPOINT",
|
|
||||||
"SCCACHE_S3_USE_SSL",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.aarch64-unknown-linux-musl]
|
|
||||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-aarch64-unknown-linux-musl:latest"
|
|
||||||
|
|
||||||
[target.arm-unknown-linux-musleabihf]
|
|
||||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-arm-unknown-linux-musleabihf:latest"
|
|
||||||
|
|
||||||
[target.armv7-unknown-linux-musleabihf]
|
|
||||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-armv7-unknown-linux-musleabihf:latest"
|
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-musl]
|
|
||||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-x86_64-unknown-linux-musl@sha256:b6d689e42f0236c8a38b961bca2a12086018b85ed20e0826310421daf182e2bb"
|
|
94
DEPLOY.md
94
DEPLOY.md
|
@ -7,44 +7,54 @@
|
||||||
|
|
||||||
## Installing Conduit
|
## Installing Conduit
|
||||||
|
|
||||||
Although you might be able to compile Conduit for Windows, we do recommend running it on a linux server. We therefore
|
Although you might be able to compile Conduit for Windows, we do recommend running it on a Linux server. We therefore
|
||||||
only offer Linux binaries.
|
only offer Linux binaries.
|
||||||
|
|
||||||
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the right url:
|
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the appropriate url:
|
||||||
|
|
||||||
| CPU Architecture | Download stable version | Download development version |
|
**Stable versions:**
|
||||||
| ------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
||||||
| x84_64 / amd64 (Most servers and computers) | [Binary][x84_64-glibc-master] / [.deb][x84_64-glibc-master-deb] | [Binary][x84_64-glibc-next] / [.deb][x84_64-glibc-next-deb] |
|
| CPU Architecture | Download stable version |
|
||||||
| armv7 (e.g. Raspberry Pi by default) | [Binary][armv7-glibc-master] / [.deb][armv7-glibc-master-deb] | [Binary][armv7-glibc-next] / [.deb][armv7-glibc-next-deb] |
|
| ------------------------------------------- | --------------------------------------------------------------- |
|
||||||
| armv8 / aarch64 | [Binary][armv8-glibc-master] / [.deb][armv8-glibc-master-deb] | [Binary][armv8-glibc-next] / [.deb][armv8-glibc-next-deb] |
|
| x84_64 / amd64 (Most servers and computers) | [Binary][x84_64-glibc-master] / [.deb][x84_64-glibc-master-deb] |
|
||||||
|
| armv7 (e.g. Raspberry Pi by default) | [Binary][armv7-glibc-master] / [.deb][armv7-glibc-master-deb] |
|
||||||
|
| armv8 / aarch64 | [Binary][armv8-glibc-master] / [.deb][armv8-glibc-master-deb] |
|
||||||
|
|
||||||
These builds were created on and linked against the glibc version shipped with Debian bullseye.
|
These builds were created on and linked against the glibc version shipped with Debian bullseye.
|
||||||
If you use a system with an older glibc version, you might need to compile Conduit yourself.
|
If you use a system with an older glibc version (e.g. RHEL8), you might need to compile Conduit yourself.
|
||||||
|
|
||||||
[x84_64-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit?job=docker:master
|
[x84_64-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit?job=docker:master
|
||||||
[armv7-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit?job=docker:master
|
[armv7-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit?job=docker:master
|
||||||
[armv8-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit?job=docker:master
|
[armv8-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit?job=docker:master
|
||||||
[x84_64-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_amd64/conduit?job=docker:next
|
|
||||||
[armv7-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm_v7/conduit?job=docker:next
|
|
||||||
[armv8-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm64/conduit?job=docker:next
|
|
||||||
[x84_64-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit.deb?job=docker:master
|
[x84_64-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit.deb?job=docker:master
|
||||||
[armv7-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit.deb?job=docker:master
|
[armv7-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit.deb?job=docker:master
|
||||||
[armv8-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit.deb?job=docker:master
|
[armv8-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit.deb?job=docker:master
|
||||||
[x84_64-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_amd64/conduit.deb?job=docker:next
|
|
||||||
[armv7-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm_v7/conduit.deb?job=docker:next
|
**Latest versions:**
|
||||||
[armv8-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm64/conduit.deb?job=docker:next
|
|
||||||
|
| Target | Type | Download |
|
||||||
|
|-|-|-|
|
||||||
|
| `x86_64-unknown-linux-gnu` | Dynamically linked Debian package | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit.deb?job=debian:x86_64-unknown-linux-gnu) |
|
||||||
|
| `x86_64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit?job=static:x86_64-unknown-linux-musl) |
|
||||||
|
| `aarch64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit?job=static:aarch64-unknown-linux-musl) |
|
||||||
|
| `x86_64-unknown-linux-musl` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-amd64.tar.gz?job=oci-image:x86_64-unknown-linux-musl) |
|
||||||
|
| `aarch64-unknown-linux-musl` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-arm64v8.tar.gz?job=oci-image:aarch64-unknown-linux-musl) |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo wget -O /usr/local/bin/matrix-conduit <url>
|
$ sudo wget -O /usr/local/bin/matrix-conduit <url>
|
||||||
$ sudo chmod +x /usr/local/bin/matrix-conduit
|
$ sudo chmod +x /usr/local/bin/matrix-conduit
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you may compile the binary yourself
|
Alternatively, you may compile the binary yourself. First, install any dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Debian
|
||||||
$ sudo apt install libclang-dev build-essential
|
$ sudo apt install libclang-dev build-essential
|
||||||
```
|
|
||||||
|
|
||||||
|
# RHEL
|
||||||
|
$ sudo dnf install clang
|
||||||
|
```
|
||||||
|
Then, `cd` into the source tree of conduit-next and run:
|
||||||
```bash
|
```bash
|
||||||
$ cargo build --release
|
$ cargo build --release
|
||||||
```
|
```
|
||||||
|
@ -74,10 +84,10 @@ cross build --release --no-default-features --features conduit_bin,backend_rocks
|
||||||
While Conduit can run as any user it is usually better to use dedicated users for different services. This also allows
|
While Conduit can run as any user it is usually better to use dedicated users for different services. This also allows
|
||||||
you to make sure that the file permissions are correctly set up.
|
you to make sure that the file permissions are correctly set up.
|
||||||
|
|
||||||
In Debian you can use this command to create a Conduit user:
|
In Debian or RHEL, you can use this command to create a Conduit user:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo adduser --system conduit --no-create-home
|
sudo adduser --system conduit --group --disabled-login --no-create-home
|
||||||
```
|
```
|
||||||
|
|
||||||
## Forwarding ports in the firewall or the router
|
## Forwarding ports in the firewall or the router
|
||||||
|
@ -86,6 +96,19 @@ Conduit uses the ports 443 and 8448 both of which need to be open in the firewal
|
||||||
|
|
||||||
If Conduit runs behind a router or in a container and has a different public IP address than the host system these public ports need to be forwarded directly or indirectly to the port mentioned in the config.
|
If Conduit runs behind a router or in a container and has a different public IP address than the host system these public ports need to be forwarded directly or indirectly to the port mentioned in the config.
|
||||||
|
|
||||||
|
## Optional: Avoid port 8448
|
||||||
|
|
||||||
|
If Conduit runs behind Cloudflare reverse proxy, which doesn't support port 8448 on free plans, [delegation](https://matrix-org.github.io/synapse/latest/delegate.html) can be set up to have federation traffic routed to port 443:
|
||||||
|
```apache
|
||||||
|
# .well-known delegation on Apache
|
||||||
|
<Files "/.well-known/matrix/server">
|
||||||
|
ErrorDocument 200 '{"m.server": "your.server.name:443"}'
|
||||||
|
Header always set Content-Type application/json
|
||||||
|
Header always set Access-Control-Allow-Origin *
|
||||||
|
</Files>
|
||||||
|
```
|
||||||
|
[SRV DNS record](https://spec.matrix.org/latest/server-server-api/#resolving-server-names) delegation is also [possible](https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-srv-record/).
|
||||||
|
|
||||||
## Setting up a systemd service
|
## Setting up a systemd service
|
||||||
|
|
||||||
Now we'll set up a systemd service for Conduit, so it's easy to start/stop Conduit and set it to autostart when your
|
Now we'll set up a systemd service for Conduit, so it's easy to start/stop Conduit and set it to autostart when your
|
||||||
|
@ -100,7 +123,7 @@ After=network.target
|
||||||
[Service]
|
[Service]
|
||||||
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
|
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
|
||||||
User=conduit
|
User=conduit
|
||||||
Group=nogroup
|
Group=conduit
|
||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=/usr/local/bin/matrix-conduit
|
ExecStart=/usr/local/bin/matrix-conduit
|
||||||
|
|
||||||
|
@ -155,7 +178,9 @@ max_request_size = 20_000_000 # in bytes
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
|
|
||||||
allow_federation = true
|
allow_federation = true
|
||||||
|
allow_check_for_updates = true
|
||||||
|
|
||||||
|
# Server to get public keys from. You probably shouldn't change this
|
||||||
trusted_servers = ["matrix.org"]
|
trusted_servers = ["matrix.org"]
|
||||||
|
|
||||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||||
|
@ -168,7 +193,7 @@ address = "127.0.0.1" # This makes sure Conduit can only be reached using the re
|
||||||
## Setting the correct file permissions
|
## Setting the correct file permissions
|
||||||
|
|
||||||
As we are using a Conduit specific user we need to allow it to read the config. To do that you can run this command on
|
As we are using a Conduit specific user we need to allow it to read the config. To do that you can run this command on
|
||||||
Debian:
|
Debian or RHEL:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chown -R root:root /etc/matrix-conduit
|
sudo chown -R root:root /etc/matrix-conduit
|
||||||
|
@ -179,7 +204,7 @@ If you use the default database path you also need to run this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir -p /var/lib/matrix-conduit/
|
sudo mkdir -p /var/lib/matrix-conduit/
|
||||||
sudo chown -R conduit:nogroup /var/lib/matrix-conduit/
|
sudo chown -R conduit:conduit /var/lib/matrix-conduit/
|
||||||
sudo chmod 700 /var/lib/matrix-conduit/
|
sudo chmod 700 /var/lib/matrix-conduit/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -192,6 +217,11 @@ This depends on whether you use Apache, Caddy, Nginx or another web server.
|
||||||
Create `/etc/apache2/sites-enabled/050-conduit.conf` and copy-and-paste this:
|
Create `/etc/apache2/sites-enabled/050-conduit.conf` and copy-and-paste this:
|
||||||
|
|
||||||
```apache
|
```apache
|
||||||
|
# Requires mod_proxy and mod_proxy_http
|
||||||
|
#
|
||||||
|
# On Apache instance compiled from source,
|
||||||
|
# paste into httpd-ssl.conf or httpd.conf
|
||||||
|
|
||||||
Listen 8448
|
Listen 8448
|
||||||
|
|
||||||
<VirtualHost *:443 *:8448>
|
<VirtualHost *:443 *:8448>
|
||||||
|
@ -199,7 +229,7 @@ Listen 8448
|
||||||
ServerName your.server.name # EDIT THIS
|
ServerName your.server.name # EDIT THIS
|
||||||
|
|
||||||
AllowEncodedSlashes NoDecode
|
AllowEncodedSlashes NoDecode
|
||||||
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ nocanon
|
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ timeout=300 nocanon
|
||||||
ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
||||||
|
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
@ -208,7 +238,11 @@ ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
||||||
**You need to make some edits again.** When you are done, run
|
**You need to make some edits again.** When you are done, run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Debian
|
||||||
$ sudo systemctl reload apache2
|
$ sudo systemctl reload apache2
|
||||||
|
|
||||||
|
# Installed from source
|
||||||
|
$ sudo apachectl -k graceful
|
||||||
```
|
```
|
||||||
|
|
||||||
### Caddy
|
### Caddy
|
||||||
|
@ -241,12 +275,14 @@ server {
|
||||||
merge_slashes off;
|
merge_slashes off;
|
||||||
|
|
||||||
# Nginx defaults to only allow 1MB uploads
|
# Nginx defaults to only allow 1MB uploads
|
||||||
|
# Increase this to allow posting large files such as videos
|
||||||
client_max_body_size 20M;
|
client_max_body_size 20M;
|
||||||
|
|
||||||
location /_matrix/ {
|
location /_matrix/ {
|
||||||
proxy_pass http://127.0.0.1:6167$request_uri;
|
proxy_pass http://127.0.0.1:6167;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 5m;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/your.server.name/fullchain.pem; # EDIT THIS
|
ssl_certificate /etc/letsencrypt/live/your.server.name/fullchain.pem; # EDIT THIS
|
||||||
|
@ -266,11 +302,19 @@ $ sudo systemctl reload nginx
|
||||||
|
|
||||||
If you chose Caddy as your web proxy SSL certificates are handled automatically and you can skip this step.
|
If you chose Caddy as your web proxy SSL certificates are handled automatically and you can skip this step.
|
||||||
|
|
||||||
The easiest way to get an SSL certificate, if you don't have one already, is to install `certbot` and run this:
|
The easiest way to get an SSL certificate, if you don't have one already, is to [install](https://certbot.eff.org/instructions) `certbot` and run this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# To use ECC for the private key,
|
||||||
|
# paste into /etc/letsencrypt/cli.ini:
|
||||||
|
# key-type = ecdsa
|
||||||
|
# elliptic-curve = secp384r1
|
||||||
|
|
||||||
$ sudo certbot -d your.server.name
|
$ sudo certbot -d your.server.name
|
||||||
```
|
```
|
||||||
|
[Automated renewal](https://eff-certbot.readthedocs.io/en/stable/using.html#automated-renewals) is usually preconfigured.
|
||||||
|
|
||||||
|
If using Cloudflare, configure instead the edge and origin certificates in dashboard. In case you’re already running a website on the same Apache server, you can just copy-and-paste the SSL configuration from your main virtual host on port 443 into the above-mentioned vhost.
|
||||||
|
|
||||||
## You're done!
|
## You're done!
|
||||||
|
|
||||||
|
@ -294,6 +338,8 @@ You can also use these commands as a quick health check.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ curl https://your.server.name/_matrix/client/versions
|
$ curl https://your.server.name/_matrix/client/versions
|
||||||
|
|
||||||
|
# If using port 8448
|
||||||
$ curl https://your.server.name:8448/_matrix/client/versions
|
$ curl https://your.server.name:8448/_matrix/client/versions
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
130
Dockerfile
130
Dockerfile
|
@ -1,130 +0,0 @@
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM docker.io/rust:1.64-bullseye AS builder
|
|
||||||
WORKDIR /usr/src/conduit
|
|
||||||
|
|
||||||
# Install required packages to build Conduit and it's dependencies
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get -y --no-install-recommends install libclang-dev=1:11.0-51+nmu5
|
|
||||||
|
|
||||||
# == Build dependencies without our own code separately for caching ==
|
|
||||||
#
|
|
||||||
# Need a fake main.rs since Cargo refuses to build anything otherwise.
|
|
||||||
#
|
|
||||||
# See https://github.com/rust-lang/cargo/issues/2644 for a Cargo feature
|
|
||||||
# request that would allow just dependencies to be compiled, presumably
|
|
||||||
# regardless of whether source files are available.
|
|
||||||
RUN mkdir src && touch src/lib.rs && echo 'fn main() {}' > src/main.rs
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
|
||||||
RUN cargo build --release && rm -r src
|
|
||||||
|
|
||||||
# Copy over actual Conduit sources
|
|
||||||
COPY src src
|
|
||||||
|
|
||||||
# main.rs and lib.rs need their timestamp updated for this to work correctly since
|
|
||||||
# otherwise the build with the fake main.rs from above is newer than the
|
|
||||||
# source files (COPY preserves timestamps).
|
|
||||||
#
|
|
||||||
# Builds conduit and places the binary at /usr/src/conduit/target/release/conduit
|
|
||||||
RUN touch src/main.rs && touch src/lib.rs && cargo build --release
|
|
||||||
|
|
||||||
|
|
||||||
# ONLY USEFUL FOR CI: target stage to extract build artifacts
|
|
||||||
FROM scratch AS builder-result
|
|
||||||
COPY --from=builder /usr/src/conduit/target/release/conduit /conduit
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
# Build cargo-deb, a tool to package up rust binaries into .deb packages for Debian/Ubuntu based systems:
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
FROM docker.io/rust:1.64-bullseye AS build-cargo-deb
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
dpkg \
|
|
||||||
dpkg-dev \
|
|
||||||
liblzma-dev
|
|
||||||
|
|
||||||
RUN cargo install cargo-deb
|
|
||||||
# => binary is in /usr/local/cargo/bin/cargo-deb
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
# Package conduit build-result into a .deb package:
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
FROM builder AS packager
|
|
||||||
WORKDIR /usr/src/conduit
|
|
||||||
|
|
||||||
COPY ./LICENSE ./LICENSE
|
|
||||||
COPY ./README.md ./README.md
|
|
||||||
COPY debian/README.Debian ./debian/
|
|
||||||
COPY --from=build-cargo-deb /usr/local/cargo/bin/cargo-deb /usr/local/cargo/bin/cargo-deb
|
|
||||||
|
|
||||||
# --no-build makes cargo-deb reuse already compiled project
|
|
||||||
RUN cargo deb --no-build
|
|
||||||
# => Package is in /usr/src/conduit/target/debian/<project_name>_<version>_<arch>.deb
|
|
||||||
|
|
||||||
|
|
||||||
# ONLY USEFUL FOR CI: target stage to extract build artifacts
|
|
||||||
FROM scratch AS packager-result
|
|
||||||
COPY --from=packager /usr/src/conduit/target/debian/*.deb /conduit.deb
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
# Stuff below this line actually ends up in the resulting docker image
|
|
||||||
# ---------------------------------------------------------------------------------------------------------------
|
|
||||||
FROM docker.io/debian:bullseye-slim AS runner
|
|
||||||
|
|
||||||
# Standard port on which Conduit launches.
|
|
||||||
# You still need to map the port when using the docker command or docker-compose.
|
|
||||||
EXPOSE 6167
|
|
||||||
|
|
||||||
ARG DEFAULT_DB_PATH=/var/lib/matrix-conduit
|
|
||||||
|
|
||||||
ENV CONDUIT_PORT=6167 \
|
|
||||||
CONDUIT_ADDRESS="0.0.0.0" \
|
|
||||||
CONDUIT_DATABASE_PATH=${DEFAULT_DB_PATH} \
|
|
||||||
CONDUIT_CONFIG=''
|
|
||||||
# └─> Set no config file to do all configuration with env vars
|
|
||||||
|
|
||||||
# Conduit needs:
|
|
||||||
# dpkg: to install conduit.deb
|
|
||||||
# ca-certificates: for https
|
|
||||||
# iproute2 & wget: for the healthcheck script
|
|
||||||
RUN apt-get update && apt-get -y --no-install-recommends install \
|
|
||||||
dpkg \
|
|
||||||
ca-certificates \
|
|
||||||
iproute2 \
|
|
||||||
wget \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Test if Conduit is still alive, uses the same endpoint as Element
|
|
||||||
COPY ./docker/healthcheck.sh /srv/conduit/healthcheck.sh
|
|
||||||
HEALTHCHECK --start-period=5s --interval=5s CMD ./healthcheck.sh
|
|
||||||
|
|
||||||
# Install conduit.deb:
|
|
||||||
COPY --from=packager /usr/src/conduit/target/debian/*.deb /srv/conduit/
|
|
||||||
RUN dpkg -i /srv/conduit/*.deb
|
|
||||||
|
|
||||||
# Improve security: Don't run stuff as root, that does not need to run as root
|
|
||||||
# Most distros also use 1000:1000 for the first real user, so this should resolve volume mounting problems.
|
|
||||||
ARG USER_ID=1000
|
|
||||||
ARG GROUP_ID=1000
|
|
||||||
RUN set -x ; \
|
|
||||||
groupadd -r -g ${GROUP_ID} conduit ; \
|
|
||||||
useradd -l -r -M -d /srv/conduit -o -u ${USER_ID} -g conduit conduit && exit 0 ; exit 1
|
|
||||||
|
|
||||||
# Create database directory, change ownership of Conduit files to conduit user and group and make the healthcheck executable:
|
|
||||||
RUN chown -cR conduit:conduit /srv/conduit && \
|
|
||||||
chmod +x /srv/conduit/healthcheck.sh && \
|
|
||||||
mkdir -p ${DEFAULT_DB_PATH} && \
|
|
||||||
chown -cR conduit:conduit ${DEFAULT_DB_PATH}
|
|
||||||
|
|
||||||
# Change user to conduit, no root permissions afterwards:
|
|
||||||
USER conduit
|
|
||||||
# Set container home directory
|
|
||||||
WORKDIR /srv/conduit
|
|
||||||
|
|
||||||
# Run Conduit and print backtraces on panics
|
|
||||||
ENV RUST_BACKTRACE=1
|
|
||||||
ENTRYPOINT [ "/usr/sbin/matrix-conduit" ]
|
|
|
@ -16,10 +16,7 @@ friends or company.
|
||||||
#### Can I try it out?
|
#### Can I try it out?
|
||||||
|
|
||||||
Yes! You can test our Conduit instance by opening a Matrix client (<https://app.element.io> or Element Android for
|
Yes! You can test our Conduit instance by opening a Matrix client (<https://app.element.io> or Element Android for
|
||||||
example) and registering on the `conduit.rs` homeserver.
|
example) and registering on the `conduit.rs` homeserver. The registration token is "for_testing_only". Don't share personal information.
|
||||||
|
|
||||||
*Registration is currently disabled because of scammers. For an account please
|
|
||||||
message us (see contact section below).*
|
|
||||||
|
|
||||||
Server hosting for conduit.rs is donated by the Matrix.org Foundation.
|
Server hosting for conduit.rs is donated by the Matrix.org Foundation.
|
||||||
|
|
||||||
|
@ -39,7 +36,7 @@ Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit
|
||||||
#### How can I deploy my own?
|
#### How can I deploy my own?
|
||||||
|
|
||||||
- Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
|
- Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
|
||||||
- Debian package: [debian/README.Debian](debian/README.Debian)
|
- Debian package: [debian/README.md](debian/README.md)
|
||||||
- Nix/NixOS: [nix/README.md](nix/README.md)
|
- Nix/NixOS: [nix/README.md](nix/README.md)
|
||||||
- Docker: [docker/README.md](docker/README.md)
|
- Docker: [docker/README.md](docker/README.md)
|
||||||
|
|
||||||
|
@ -68,7 +65,7 @@ Thanks to the contributors to Conduit and all libraries we use, for example:
|
||||||
If you run into any question, feel free to
|
If you run into any question, feel free to
|
||||||
- Ask us in `#conduit:fachschaften.org` on Matrix
|
- Ask us in `#conduit:fachschaften.org` on Matrix
|
||||||
- Write an E-Mail to `conduit@koesters.xyz`
|
- Write an E-Mail to `conduit@koesters.xyz`
|
||||||
- Send an direct message to `timo@fachschaften.org` on Matrix
|
- Send an direct message to `timokoesters@fachschaften.org` on Matrix
|
||||||
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
|
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
|
||||||
|
|
||||||
#### Donate
|
#### Donate
|
||||||
|
|
37
bin/complement
Executable file
37
bin/complement
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Path to Complement's source code
|
||||||
|
COMPLEMENT_SRC="$1"
|
||||||
|
|
||||||
|
# A `.jsonl` file to write test logs to
|
||||||
|
LOG_FILE="$2"
|
||||||
|
|
||||||
|
# A `.jsonl` file to write test results to
|
||||||
|
RESULTS_FILE="$3"
|
||||||
|
|
||||||
|
OCI_IMAGE="complement-conduit:dev"
|
||||||
|
|
||||||
|
env \
|
||||||
|
-C "$(git rev-parse --show-toplevel)" \
|
||||||
|
docker build \
|
||||||
|
--tag "$OCI_IMAGE" \
|
||||||
|
--file complement/Dockerfile \
|
||||||
|
.
|
||||||
|
|
||||||
|
# It's okay (likely, even) that `go test` exits nonzero
|
||||||
|
set +o pipefail
|
||||||
|
env \
|
||||||
|
-C "$COMPLEMENT_SRC" \
|
||||||
|
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
|
||||||
|
go test -json ./tests | tee "$LOG_FILE"
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
# Post-process the results into an easy-to-compare format
|
||||||
|
cat "$LOG_FILE" | jq -c '
|
||||||
|
select(
|
||||||
|
(.Action == "pass" or .Action == "fail" or .Action == "skip")
|
||||||
|
and .Test != null
|
||||||
|
) | {Action: .Action, Test: .Test}
|
||||||
|
' | sort > "$RESULTS_FILE"
|
31
bin/nix-build-and-cache
Executable file
31
bin/nix-build-and-cache
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# The first argument must be the desired installable
|
||||||
|
INSTALLABLE="$1"
|
||||||
|
|
||||||
|
# Build the installable and forward any other arguments too
|
||||||
|
nix build "$@"
|
||||||
|
|
||||||
|
if [ ! -z ${ATTIC_TOKEN+x} ]; then
|
||||||
|
|
||||||
|
nix run --inputs-from . attic -- login \
|
||||||
|
conduit \
|
||||||
|
https://nix.computer.surgery/conduit \
|
||||||
|
"$ATTIC_TOKEN"
|
||||||
|
|
||||||
|
push_args=(
|
||||||
|
# Attic and its build dependencies
|
||||||
|
"$(nix path-info --inputs-from . attic)"
|
||||||
|
"$(nix path-info --inputs-from . attic --derivation)"
|
||||||
|
|
||||||
|
# The target installable and its build dependencies
|
||||||
|
"$(nix path-info "$INSTALLABLE" --derivation)"
|
||||||
|
"$(nix path-info "$INSTALLABLE")"
|
||||||
|
)
|
||||||
|
|
||||||
|
nix run --inputs-from . attic -- push conduit "${push_args[@]}"
|
||||||
|
else
|
||||||
|
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
|
||||||
|
fi
|
|
@ -1,26 +1,30 @@
|
||||||
# For use in our CI only. This requires a build artifact created by a previous run pipline stage to be placed in cached_target/release/conduit
|
FROM rust:1.75.0
|
||||||
FROM registry.gitlab.com/jfowl/conduit-containers/rust-with-tools:commit-16a08e9b as builder
|
|
||||||
#FROM rust:latest as builder
|
|
||||||
|
|
||||||
WORKDIR /workdir
|
WORKDIR /workdir
|
||||||
|
|
||||||
ARG RUSTC_WRAPPER
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ARG AWS_ACCESS_KEY_ID
|
libclang-dev
|
||||||
ARG AWS_SECRET_ACCESS_KEY
|
|
||||||
ARG SCCACHE_BUCKET
|
|
||||||
ARG SCCACHE_ENDPOINT
|
|
||||||
ARG SCCACHE_S3_USE_SSL
|
|
||||||
|
|
||||||
COPY . .
|
COPY Cargo.toml Cargo.toml
|
||||||
RUN mkdir -p target/release
|
COPY Cargo.lock Cargo.lock
|
||||||
RUN test -e cached_target/release/conduit && cp cached_target/release/conduit target/release/conduit || cargo build --release
|
COPY src src
|
||||||
|
RUN cargo build --release \
|
||||||
## Actual image
|
&& mv target/release/conduit conduit \
|
||||||
FROM debian:bullseye
|
&& rm -rf target
|
||||||
WORKDIR /workdir
|
|
||||||
|
|
||||||
# Install caddy
|
# Install caddy
|
||||||
RUN apt-get update && apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-testing.list && apt-get update && apt-get install -y caddy
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
debian-keyring \
|
||||||
|
debian-archive-keyring \
|
||||||
|
apt-transport-https \
|
||||||
|
curl \
|
||||||
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' \
|
||||||
|
| gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg \
|
||||||
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' \
|
||||||
|
| tee /etc/apt/sources.list.d/caddy-testing.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y caddy
|
||||||
|
|
||||||
COPY conduit-example.toml conduit.toml
|
COPY conduit-example.toml conduit.toml
|
||||||
COPY complement/caddy.json caddy.json
|
COPY complement/caddy.json caddy.json
|
||||||
|
@ -29,15 +33,9 @@ ENV SERVER_NAME=localhost
|
||||||
ENV CONDUIT_CONFIG=/workdir/conduit.toml
|
ENV CONDUIT_CONFIG=/workdir/conduit.toml
|
||||||
|
|
||||||
RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml
|
RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml
|
||||||
RUN echo "allow_federation = true" >> conduit.toml
|
|
||||||
RUN echo "allow_encryption = true" >> conduit.toml
|
|
||||||
RUN echo "allow_registration = true" >> conduit.toml
|
|
||||||
RUN echo "log = \"warn,_=off,sled=off\"" >> conduit.toml
|
RUN echo "log = \"warn,_=off,sled=off\"" >> conduit.toml
|
||||||
RUN sed -i "s/address = \"127.0.0.1\"/address = \"0.0.0.0\"/g" conduit.toml
|
RUN sed -i "s/address = \"127.0.0.1\"/address = \"0.0.0.0\"/g" conduit.toml
|
||||||
|
|
||||||
COPY --from=builder /workdir/target/release/conduit /workdir/conduit
|
|
||||||
RUN chmod +x /workdir/conduit
|
|
||||||
|
|
||||||
EXPOSE 8008 8448
|
EXPOSE 8008 8448
|
||||||
|
|
||||||
CMD uname -a && \
|
CMD uname -a && \
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
# Running Conduit on Complement
|
# Complement
|
||||||
|
|
||||||
This assumes that you're familiar with complement, if not, please readme
|
## What's that?
|
||||||
[their readme](https://github.com/matrix-org/complement#running).
|
|
||||||
|
|
||||||
Complement works with "base images", this directory (and Dockerfile) helps build the conduit complement-ready docker
|
Have a look at [its repository](https://github.com/matrix-org/complement).
|
||||||
image.
|
|
||||||
|
|
||||||
To build, `cd` to the base directory of the workspace, and run this:
|
## How do I use it with Conduit?
|
||||||
|
|
||||||
`docker build -t complement-conduit:dev -f complement/Dockerfile .`
|
The script at [`../bin/complement`](../bin/complement) has automation for this.
|
||||||
|
It takes a few command line arguments, you can read the script to find out what
|
||||||
Then use `complement-conduit:dev` as a base image for running complement tests.
|
those are.
|
||||||
|
|
|
@ -39,6 +39,7 @@ max_request_size = 20_000_000 # in bytes
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
|
|
||||||
allow_federation = true
|
allow_federation = true
|
||||||
|
allow_check_for_updates = true
|
||||||
|
|
||||||
# Enable the display name lightning bolt on registration.
|
# Enable the display name lightning bolt on registration.
|
||||||
enable_lightning_bolt = true
|
enable_lightning_bolt = true
|
||||||
|
|
18
debian/README.Debian → debian/README.md
vendored
18
debian/README.Debian → debian/README.md
vendored
|
@ -1,28 +1,36 @@
|
||||||
Conduit for Debian
|
Conduit for Debian
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Information about downloading, building and deploying the Debian package, see
|
||||||
|
the "Installing Conduit" section in [DEPLOY.md](../DEPLOY.md).
|
||||||
|
All following sections until "Setting up the Reverse Proxy" be ignored because
|
||||||
|
this is handled automatically by the packaging.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
When installed, Debconf generates the configuration of the homeserver
|
When installed, Debconf generates the configuration of the homeserver
|
||||||
(host)name, the address and port it listens on. This configuration ends up in
|
(host)name, the address and port it listens on. This configuration ends up in
|
||||||
/etc/matrix-conduit/conduit.toml.
|
`/etc/matrix-conduit/conduit.toml`.
|
||||||
|
|
||||||
You can tweak more detailed settings by uncommenting and setting the variables
|
You can tweak more detailed settings by uncommenting and setting the variables
|
||||||
in /etc/matrix-conduit/conduit.toml. This involves settings such as the maximum
|
in `/etc/matrix-conduit/conduit.toml`. This involves settings such as the maximum
|
||||||
file size for download/upload, enabling federation, etc.
|
file size for download/upload, enabling federation, etc.
|
||||||
|
|
||||||
Running
|
Running
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The package uses the matrix-conduit.service systemd unit file to start and
|
The package uses the `matrix-conduit.service` systemd unit file to start and
|
||||||
stop Conduit. It loads the configuration file mentioned above to set up the
|
stop Conduit. It loads the configuration file mentioned above to set up the
|
||||||
environment before running the server.
|
environment before running the server.
|
||||||
|
|
||||||
This package assumes by default that Conduit will be placed behind a reverse
|
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
|
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
|
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
|
||||||
http://localhost:6167.
|
<http://localhost:6167>.
|
||||||
|
|
||||||
At a later stage this packaging may support also setting up TLS and running
|
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
|
stand-alone. In this case, however, you need to set up some certificates and
|
7
debian/postinst
vendored
7
debian/postinst
vendored
|
@ -19,11 +19,11 @@ case "$1" in
|
||||||
_matrix-conduit
|
_matrix-conduit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create the database path if it does not exist yet.
|
# Create the database path if it does not exist yet and fix up ownership
|
||||||
if [ ! -d "$CONDUIT_DATABASE_PATH" ]; then
|
# and permissions.
|
||||||
mkdir -p "$CONDUIT_DATABASE_PATH"
|
mkdir -p "$CONDUIT_DATABASE_PATH"
|
||||||
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
||||||
fi
|
chmod 700 "$CONDUIT_DATABASE_PATH"
|
||||||
|
|
||||||
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
|
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
|
||||||
# Write the debconf values in the config.
|
# Write the debconf values in the config.
|
||||||
|
@ -73,6 +73,7 @@ max_request_size = 20_000_000 # in bytes
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
|
|
||||||
allow_federation = true
|
allow_federation = true
|
||||||
|
allow_check_for_updates = true
|
||||||
|
|
||||||
trusted_servers = ["matrix.org"]
|
trusted_servers = ["matrix.org"]
|
||||||
|
|
||||||
|
|
10
default.nix
Normal file
10
default.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
(import
|
||||||
|
(
|
||||||
|
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
||||||
|
fetchTarball {
|
||||||
|
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{ src = ./.; }
|
||||||
|
).defaultNix
|
110
docker/README.md
110
docker/README.md
|
@ -4,7 +4,36 @@
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
### Build & Dockerfile
|
To run Conduit with Docker you can either build the image yourself or pull it from a registry.
|
||||||
|
|
||||||
|
|
||||||
|
### Use a registry
|
||||||
|
|
||||||
|
OCI images for Conduit are available in the registries listed below. We recommend using the image tagged as `latest` from GitLab's own registry.
|
||||||
|
|
||||||
|
| Registry | Image | Size | Notes |
|
||||||
|
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
|
||||||
|
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:latest][gl] | ![Image Size][shield-latest] | Stable image. |
|
||||||
|
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:latest][dh] | ![Image Size][shield-latest] | Stable image. |
|
||||||
|
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:next][gl] | ![Image Size][shield-next] | Development version. |
|
||||||
|
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:next][dh] | ![Image Size][shield-next] | Development version. |
|
||||||
|
|
||||||
|
|
||||||
|
[dh]: https://hub.docker.com/r/matrixconduit/matrix-conduit
|
||||||
|
[gl]: https://gitlab.com/famedly/conduit/container_registry/2497937
|
||||||
|
[shield-latest]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/latest
|
||||||
|
[shield-next]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/next
|
||||||
|
|
||||||
|
|
||||||
|
Use
|
||||||
|
```bash
|
||||||
|
docker image pull <link>
|
||||||
|
```
|
||||||
|
to pull it to your machine.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Build using a dockerfile
|
||||||
|
|
||||||
The Dockerfile provided by Conduit has two stages, each of which creates an image.
|
The Dockerfile provided by Conduit has two stages, each of which creates an image.
|
||||||
|
|
||||||
|
@ -19,9 +48,11 @@ docker build --tag matrixconduit/matrix-conduit:latest .
|
||||||
|
|
||||||
which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`.
|
which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
After building the image you can simply run it with
|
When you have the image you can simply run it with
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d -p 8448:6167 \
|
docker run -d -p 8448:6167 \
|
||||||
|
@ -34,19 +65,10 @@ docker run -d -p 8448:6167 \
|
||||||
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
||||||
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
||||||
-e CONDUIT_LOG="warn,rocket=off,_=off,sled=off" \
|
-e CONDUIT_LOG="warn,rocket=off,_=off,sled=off" \
|
||||||
--name conduit matrixconduit/matrix-conduit:latest
|
--name conduit <link>
|
||||||
```
|
```
|
||||||
|
|
||||||
or you can skip the build step and pull the image from one of the following registries:
|
or you can use [docker-compose](#docker-compose).
|
||||||
|
|
||||||
| Registry | Image | Size |
|
|
||||||
| --------------- | --------------------------------------------------------------- | --------------------- |
|
|
||||||
| Docker Hub | [matrixconduit/matrix-conduit:latest][dh] | ![Image Size][shield] |
|
|
||||||
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:latest][gl] | ![Image Size][shield] |
|
|
||||||
|
|
||||||
[dh]: https://hub.docker.com/r/matrixconduit/matrix-conduit
|
|
||||||
[gl]: https://gitlab.com/famedly/conduit/container_registry/2497937
|
|
||||||
[shield]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/latest
|
|
||||||
|
|
||||||
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../conduit-example.toml).
|
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [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
|
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
|
||||||
|
@ -54,7 +76,7 @@ to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Docker-compose
|
### Docker-compose
|
||||||
|
|
||||||
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
||||||
|
|
||||||
|
@ -95,7 +117,7 @@ As a container user, you probably know about Traefik. It is a easy to use revers
|
||||||
containerized app and services available through the web. With the two provided files,
|
containerized app and services available through the web. With the two provided files,
|
||||||
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and
|
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and
|
||||||
[`docker-compose.override.yml`](docker-compose.override.traefik.yml), it is equally easy to deploy
|
[`docker-compose.override.yml`](docker-compose.override.yml), it is equally easy to deploy
|
||||||
and use Conduit, with a little caveat. If you already took a look at the files, then you should have
|
and use Conduit, with a little caveat. If you already took a look at the files, then you should have
|
||||||
seen the `well-known` service, and that is the little caveat. Traefik is simply a proxy and
|
seen the `well-known` service, and that is the little caveat. Traefik is simply a proxy and
|
||||||
loadbalancer and is not able to serve any kind of content, but for Conduit to federate, we need to
|
loadbalancer and is not able to serve any kind of content, but for Conduit to federate, we need to
|
||||||
|
@ -106,7 +128,8 @@ With the service `well-known` we use a single `nginx` container that will serve
|
||||||
|
|
||||||
So...step by step:
|
So...step by step:
|
||||||
|
|
||||||
1. Copy [`docker-compose.traefik.yml`](docker-compose.traefik.yml) and [`docker-compose.override.traefik.yml`](docker-compose.override.traefik.yml) from the repository and remove `.traefik` from the filenames.
|
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||||
|
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
|
||||||
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
||||||
3. Create the `conduit.toml` config file, an example can be found [here](../conduit-example.toml), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
3. Create the `conduit.toml` config file, an example can be found [here](../conduit-example.toml), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
||||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||||
|
@ -138,3 +161,58 @@ So...step by step:
|
||||||
|
|
||||||
6. Run `docker-compose up -d`
|
6. Run `docker-compose up -d`
|
||||||
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
|
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Voice communication
|
||||||
|
|
||||||
|
In order to make or receive calls, a TURN server is required. Conduit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Create a configuration file called `coturn.conf` containing:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
use-auth-secret
|
||||||
|
static-auth-secret=<a secret key>
|
||||||
|
realm=<your server domain>
|
||||||
|
```
|
||||||
|
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||||
|
|
||||||
|
These same values need to be set in conduit. You can either modify conduit.toml to include these lines:
|
||||||
|
```
|
||||||
|
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
|
||||||
|
turn_secret = "<secret key from coturn configuration>"
|
||||||
|
```
|
||||||
|
or append the following to the docker environment variables dependig on which configuration method you used earlier:
|
||||||
|
```yml
|
||||||
|
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
|
||||||
|
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
|
||||||
|
```
|
||||||
|
Restart Conduit to apply these changes.
|
||||||
|
|
||||||
|
### Run
|
||||||
|
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||||
|
```bash
|
||||||
|
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||||
|
```
|
||||||
|
|
||||||
|
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
|
||||||
|
and run `docker-compose up -d` in the same directory.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: 3
|
||||||
|
services:
|
||||||
|
turn:
|
||||||
|
container_name: coturn-server
|
||||||
|
image: docker.io/coturn/coturn
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "host"
|
||||||
|
volumes:
|
||||||
|
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
|
||||||
|
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ services:
|
||||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||||
|
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
||||||
|
|
|
@ -35,8 +35,9 @@ services:
|
||||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
# 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: "warn,_=off,sled=off"
|
# CONDUIT_LOG: info # default is: "warn,_=off,sled=off"
|
||||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||||
# CONDUIT_ALLOW_ENCRYPTION: 'false'
|
# CONDUIT_ALLOW_ENCRYPTION: 'true'
|
||||||
# CONDUIT_ALLOW_FEDERATION: 'false'
|
# CONDUIT_ALLOW_FEDERATION: 'true'
|
||||||
|
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||||
# CONDUIT_WORKERS: 10
|
# CONDUIT_WORKERS: 10
|
||||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||||
|
|
|
@ -29,6 +29,7 @@ services:
|
||||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||||
|
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
64
engage.toml
Normal file
64
engage.toml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
interpreter = ["bash", "-euo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "engage"
|
||||||
|
group = "versions"
|
||||||
|
script = "engage --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "rustc"
|
||||||
|
group = "versions"
|
||||||
|
script = "rustc --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo"
|
||||||
|
group = "versions"
|
||||||
|
script = "cargo --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo-fmt"
|
||||||
|
group = "versions"
|
||||||
|
script = "cargo fmt --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "rustdoc"
|
||||||
|
group = "versions"
|
||||||
|
script = "rustdoc --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo-clippy"
|
||||||
|
group = "versions"
|
||||||
|
script = "cargo clippy -- --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo-fmt"
|
||||||
|
group = "lints"
|
||||||
|
script = "cargo fmt --check -- --color=always"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo-doc"
|
||||||
|
group = "lints"
|
||||||
|
script = """
|
||||||
|
RUSTDOCFLAGS="-D warnings" cargo doc \
|
||||||
|
--workspace \
|
||||||
|
--no-deps \
|
||||||
|
--document-private-items \
|
||||||
|
--color always
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo-clippy"
|
||||||
|
group = "lints"
|
||||||
|
script = "cargo clippy --workspace --all-targets --color=always -- -D warnings"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "cargo"
|
||||||
|
group = "tests"
|
||||||
|
script = """
|
||||||
|
cargo test \
|
||||||
|
--workspace \
|
||||||
|
--all-targets \
|
||||||
|
--color=always \
|
||||||
|
-- \
|
||||||
|
--color=always
|
||||||
|
"""
|
401
flake.lock
401
flake.lock
|
@ -1,53 +1,41 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"alejandra": {
|
"attic": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"crane": "crane",
|
||||||
"flakeCompat": "flakeCompat",
|
"flake-compat": "flake-compat",
|
||||||
"nixpkgs": [
|
"flake-utils": "flake-utils",
|
||||||
"d2n",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1658427149,
|
"lastModified": 1705617092,
|
||||||
"narHash": "sha256-ToD/1z/q5VHsLMrS2h96vjJoLho59eNRtknOUd19ey8=",
|
"narHash": "sha256-n9PK4O4X4S1JkwpkMuYm1wHZYJzRqif8g3RuVIPD+rY=",
|
||||||
"owner": "kamadorueda",
|
"owner": "zhaofengli",
|
||||||
"repo": "alejandra",
|
"repo": "attic",
|
||||||
"rev": "f5a22afd2adfb249b4e68e0b33aa1f0fb73fb1be",
|
"rev": "fbe252a5c21febbe920c025560cbd63b20e24f3b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "kamadorueda",
|
"owner": "zhaofengli",
|
||||||
"repo": "alejandra",
|
"ref": "main",
|
||||||
"type": "github"
|
"repo": "attic",
|
||||||
}
|
|
||||||
},
|
|
||||||
"all-cabal-json": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1665552503,
|
|
||||||
"narHash": "sha256-r14RmRSwzv5c+bWKUDaze6pXM7nOsiz1H8nvFHJvufc=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "all-cabal-json",
|
|
||||||
"rev": "d7c0434eebffb305071404edcf9d5cd99703878e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "hackage",
|
|
||||||
"repo": "all-cabal-json",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crane": {
|
"crane": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"attic",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1670900067,
|
"lastModified": 1702918879,
|
||||||
"narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=",
|
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b",
|
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -56,69 +44,40 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"d2n": {
|
"crane_2": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"alejandra": "alejandra",
|
|
||||||
"all-cabal-json": "all-cabal-json",
|
|
||||||
"crane": "crane",
|
|
||||||
"devshell": "devshell",
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"flake-utils-pre-commit": "flake-utils-pre-commit",
|
|
||||||
"ghc-utils": "ghc-utils",
|
|
||||||
"gomod2nix": "gomod2nix",
|
|
||||||
"mach-nix": "mach-nix",
|
|
||||||
"nix-pypi-fetcher": "nix-pypi-fetcher",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
]
|
||||||
"poetry2nix": "poetry2nix",
|
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674848374,
|
"lastModified": 1706473964,
|
||||||
"narHash": "sha256-1+xlsmUWzpptK8mLjznwqOLogeicLkxB8tV6XUZbobc=",
|
"narHash": "sha256-Fq6xleee/TsX6NbtoRuI96bBuDHMU57PrcK9z1QEKbk=",
|
||||||
"owner": "nix-community",
|
"owner": "ipetkov",
|
||||||
"repo": "dream2nix",
|
"repo": "crane",
|
||||||
"rev": "d91e7381fa303be02f70e472207e05b26ce35b41",
|
"rev": "c798790eabec3e3da48190ae3698ac227aab770c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "ipetkov",
|
||||||
"repo": "dream2nix",
|
"ref": "master",
|
||||||
"type": "github"
|
"repo": "crane",
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1663445644,
|
|
||||||
"narHash": "sha256-+xVlcK60x7VY1vRJbNUEAHi17ZuoQxAIH4S4iUFUGBA=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"rev": "e3dc3e21594fe07bdb24bdf1c8657acaa4cb8f66",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fenix": {
|
"fenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"d2n",
|
|
||||||
"alejandra",
|
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657607339,
|
"lastModified": 1705559032,
|
||||||
"narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=",
|
"narHash": "sha256-Cb+Jd1+Gz4Wi+8elPnUIHnqQmE1qjDRZ+PsJaPaAffY=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d",
|
"rev": "e132ea0eb0c799a2109a91688e499d7bf4962801",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -127,31 +86,45 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-compat": {
|
||||||
"inputs": {
|
"flake": false,
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668450977,
|
"lastModified": 1673956053,
|
||||||
"narHash": "sha256-cfLhMhnvXn6x1vPm+Jow3RiFAUSCw/l1utktCw5rVA4=",
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
"owner": "hercules-ci",
|
"owner": "edolstra",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-compat",
|
||||||
"rev": "d591857e9d7dd9ddbfba0ea02b43b927c3c0f1fa",
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "edolstra",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat_2": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696426674,
|
||||||
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659877975,
|
"lastModified": 1667395993,
|
||||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -160,13 +133,16 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils-pre-commit": {
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1644229661,
|
"lastModified": 1705309234,
|
||||||
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -175,213 +151,88 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flakeCompat": {
|
"nix-filter": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1650374568,
|
"lastModified": 1705332318,
|
||||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
"narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=",
|
||||||
"owner": "edolstra",
|
"owner": "numtide",
|
||||||
"repo": "flake-compat",
|
"repo": "nix-filter",
|
||||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
"rev": "3449dc925982ad46246cfc36469baf66e1b64f17",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "edolstra",
|
"owner": "numtide",
|
||||||
"repo": "flake-compat",
|
"repo": "nix-filter",
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ghc-utils": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1662774800,
|
|
||||||
"narHash": "sha256-1Rd2eohGUw/s1tfvkepeYpg8kCEXiIot0RijapUjAkE=",
|
|
||||||
"ref": "refs/heads/master",
|
|
||||||
"rev": "bb3a2d3dc52ff0253fb9c2812bd7aa2da03e0fea",
|
|
||||||
"revCount": 1072,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitlab.haskell.org/bgamari/ghc-utils"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitlab.haskell.org/bgamari/ghc-utils"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gomod2nix": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1627572165,
|
|
||||||
"narHash": "sha256-MFpwnkvQpauj799b4QTBJQFEddbD02+Ln5k92QyHOSk=",
|
|
||||||
"owner": "tweag",
|
|
||||||
"repo": "gomod2nix",
|
|
||||||
"rev": "67f22dd738d092c6ba88e420350ada0ed4992ae8",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tweag",
|
|
||||||
"repo": "gomod2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mach-nix": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1634711045,
|
|
||||||
"narHash": "sha256-m5A2Ty88NChLyFhXucECj6+AuiMZPHXNbw+9Kcs7F6Y=",
|
|
||||||
"owner": "DavHau",
|
|
||||||
"repo": "mach-nix",
|
|
||||||
"rev": "4433f74a97b94b596fa6cd9b9c0402104aceef5d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "mach-nix",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-pypi-fetcher": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1669065297,
|
|
||||||
"narHash": "sha256-UStjXjNIuIm7SzMOWvuYWIHBkPUKQ8Id63BMJjnIDoA=",
|
|
||||||
"owner": "DavHau",
|
|
||||||
"repo": "nix-pypi-fetcher",
|
|
||||||
"rev": "a9885ac6a091576b5195d547ac743d45a2a615ac",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "DavHau",
|
|
||||||
"repo": "nix-pypi-fetcher",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674641431,
|
"lastModified": 1702539185,
|
||||||
"narHash": "sha256-qfo19qVZBP4qn5M5gXc/h1MDgAtPA5VxJm9s8RUAkVk=",
|
"narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9b97ad7b4330aacda9b2343396eb3df8a853b4fc",
|
"rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixpkgs-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"lastModified": 1702780907,
|
||||||
"lastModified": 1665349835,
|
"narHash": "sha256-blbrBBXjjZt6OKTcYX1jpe9SRof2P9ZYWPzq22tzXAA=",
|
||||||
"narHash": "sha256-UK4urM3iN80UXQ7EaOappDzcisYIuEURFRoGQ/yPkug=",
|
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "34c5293a71ffdb2fe054eb5288adc1882c1eb0b1",
|
"rev": "1e2e384c5b7c50dbf8e9c441a9e58d85f408b01f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-23.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705496572,
|
||||||
|
"narHash": "sha256-rPIe9G5EBLXdBdn9ilGc0nq082lzQd0xGGe092R/5QE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "842d9d80cfd4560648c785f8a4e6f3b096790e19",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"dir": "lib",
|
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib_2": {
|
|
||||||
"locked": {
|
|
||||||
"dir": "lib",
|
|
||||||
"lastModified": 1672350804,
|
|
||||||
"narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"dir": "lib",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": "nixpkgs-lib_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1674771137,
|
|
||||||
"narHash": "sha256-Zpk1GbEsYrqKmuIZkx+f+8pU0qcCYJoSUwNz1Zk+R00=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "7c7a8bce3dffe71203dcd4276504d1cb49dfe05f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"poetry2nix": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1666918719,
|
|
||||||
"narHash": "sha256-BkK42fjAku+2WgCOv2/1NrPa754eQPV7gPBmoKQBWlc=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "poetry2nix",
|
|
||||||
"rev": "289efb187123656a116b915206e66852f038720e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "1.36.0",
|
|
||||||
"repo": "poetry2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pre-commit-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": [
|
|
||||||
"d2n",
|
|
||||||
"flake-utils-pre-commit"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"d2n",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1646153636,
|
|
||||||
"narHash": "sha256-AlWHMzK+xJ1mG267FdT8dCq/HvLCA6jwmx2ZUy5O8tY=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"rev": "b6bc0b21e1617e2b07d8205e7fae7224036dfa4b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"d2n": "d2n",
|
"attic": "attic",
|
||||||
"nixpkgs": "nixpkgs",
|
"crane": "crane_2",
|
||||||
"parts": "parts",
|
"fenix": "fenix",
|
||||||
"rust-overlay": "rust-overlay"
|
"flake-compat": "flake-compat_2",
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nix-filter": "nix-filter",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657557289,
|
"lastModified": 1705523001,
|
||||||
"narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=",
|
"narHash": "sha256-TWq5vJ6m+9HGSDMsQAmz1TMegMi79R3TTyKjnPWsQp8=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "caf23f29144b371035b864a1017dbc32573ad56d",
|
"rev": "9d9b34354d2f13e33568c9c55b226dd014a146a0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -391,24 +242,18 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"systems": {
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674786480,
|
"lastModified": 1681028828,
|
||||||
"narHash": "sha256-n25V3Ug/dJewbJaxj1gL0cUMBdOonrVkIQCHd9yHHvw=",
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
"owner": "oxalica",
|
"owner": "nix-systems",
|
||||||
"repo": "rust-overlay",
|
"repo": "default",
|
||||||
"rev": "296dd673b46aaebe1c8355f1848ceb7c905dda35",
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "oxalica",
|
"owner": "nix-systems",
|
||||||
"repo": "rust-overlay",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
301
flake.nix
301
flake.nix
|
@ -1,56 +1,259 @@
|
||||||
{
|
{
|
||||||
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
inputs = {
|
||||||
inputs.d2n.url = "github:nix-community/dream2nix";
|
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||||
inputs.d2n.inputs.nixpkgs.follows = "nixpkgs";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
inputs.parts.url = "github:hercules-ci/flake-parts";
|
nix-filter.url = "github:numtide/nix-filter";
|
||||||
inputs.rust-overlay.url = "github:oxalica/rust-overlay";
|
flake-compat = {
|
||||||
inputs.rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
url = "github:edolstra/flake-compat";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
|
||||||
outputs = inp:
|
fenix = {
|
||||||
inp.parts.lib.mkFlake {inputs = inp;} {
|
url = "github:nix-community/fenix";
|
||||||
systems = ["x86_64-linux"];
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
imports = [inp.d2n.flakeModuleBeta];
|
};
|
||||||
perSystem = {
|
crane = {
|
||||||
config,
|
url = "github:ipetkov/crane?ref=master";
|
||||||
system,
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
pkgs,
|
};
|
||||||
...
|
attic.url = "github:zhaofengli/attic?ref=main";
|
||||||
}: let
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self
|
||||||
|
, nixpkgs
|
||||||
|
, flake-utils
|
||||||
|
, nix-filter
|
||||||
|
|
||||||
|
, fenix
|
||||||
|
, crane
|
||||||
|
, ...
|
||||||
|
}: flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgsHost = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
|
# Nix-accessible `Cargo.toml`
|
||||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
pkgsWithToolchain = pkgs.appendOverlays [inp.rust-overlay.overlays.default];
|
|
||||||
|
|
||||||
toolchains = pkgsWithToolchain.rust-bin.stable."${cargoToml.package.rust-version}";
|
# The Rust toolchain to use
|
||||||
# toolchain to use when building conduit, includes only cargo and rustc to reduce closure size
|
toolchain = fenix.packages.${system}.fromToolchainFile {
|
||||||
buildToolchain = toolchains.minimal;
|
file = ./rust-toolchain.toml;
|
||||||
# toolchain to use in development shell
|
|
||||||
# the "default" component set of toolchain adds rustfmt, clippy etc.
|
# See also `rust-toolchain.toml`
|
||||||
devToolchain = toolchains.default.override {
|
sha256 = "sha256-SXRtAuO4IqNOQq+nLbrsDFbVk+3aVA8NNpSZsKlVH/8=";
|
||||||
extensions = ["rust-src"];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# flake outputs for conduit project
|
builder = pkgs:
|
||||||
conduitOutputs = config.dream2nix.outputs.conduit;
|
((crane.mkLib pkgs).overrideToolchain toolchain).buildPackage;
|
||||||
in {
|
|
||||||
dream2nix.inputs.conduit = {
|
nativeBuildInputs = pkgs: [
|
||||||
source = inp.self;
|
# bindgen needs the build platform's libclang. Apparently due to
|
||||||
projects.conduit = {
|
# "splicing weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't
|
||||||
name = "conduit";
|
# quite do the right thing here.
|
||||||
subsystem = "rust";
|
pkgs.buildPackages.rustPlatform.bindgenHook
|
||||||
translator = "cargo-lock";
|
];
|
||||||
};
|
|
||||||
packageOverrides = {
|
env = pkgs: {
|
||||||
"^.*".set-toolchain.overrideRustToolchain = _: {
|
ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb}/include";
|
||||||
cargo = buildToolchain;
|
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/lib";
|
||||||
rustc = buildToolchain;
|
}
|
||||||
};
|
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isStatic {
|
||||||
};
|
ROCKSDB_STATIC = "";
|
||||||
};
|
}
|
||||||
devShells.conduit = conduitOutputs.devShells.conduit.overrideAttrs (old: {
|
// {
|
||||||
# export default crate sources for rust-analyzer to read
|
CARGO_BUILD_RUSTFLAGS = let inherit (pkgs) lib stdenv; in
|
||||||
RUST_SRC_PATH = "${devToolchain}/lib/rustlib/src/rust/library";
|
lib.concatStringsSep " " ([]
|
||||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [devToolchain];
|
++ lib.optionals
|
||||||
});
|
# This disables PIE for static builds, which isn't great in terms
|
||||||
devShells.default = config.devShells.conduit;
|
# of security. Unfortunately, my hand is forced because nixpkgs'
|
||||||
};
|
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
||||||
};
|
# leaving PIE enabled.
|
||||||
|
stdenv.hostPlatform.isStatic
|
||||||
|
["-C" "relocation-model=static"]
|
||||||
|
++ lib.optionals
|
||||||
|
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
|
||||||
|
["-l" "c"]
|
||||||
|
++ lib.optionals
|
||||||
|
# This check has to match the one [here][0]. We only need to set
|
||||||
|
# these flags when using a different linker. Don't ask me why,
|
||||||
|
# though, because I don't know. All I know is it breaks otherwise.
|
||||||
|
#
|
||||||
|
# [0]: https://github.com/NixOS/nixpkgs/blob/612f97239e2cc474c13c9dafa0df378058c5ad8d/pkgs/build-support/rust/lib/default.nix#L36-L39
|
||||||
|
(
|
||||||
|
# Nixpkgs doesn't check for x86_64 here but we do, because I
|
||||||
|
# observed a failure building statically for x86_64 without
|
||||||
|
# including it here. Linkers are weird.
|
||||||
|
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
|
||||||
|
&& stdenv.hostPlatform.isStatic
|
||||||
|
&& !stdenv.isDarwin
|
||||||
|
&& !stdenv.cc.bintools.isLLVM
|
||||||
|
)
|
||||||
|
[
|
||||||
|
"-l"
|
||||||
|
"stdc++"
|
||||||
|
"-L"
|
||||||
|
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# What follows is stolen from [here][0]. Its purpose is to properly
|
||||||
|
# configure compilers and linkers for various stages of the build, and
|
||||||
|
# even covers the case of build scripts that need native code compiled and
|
||||||
|
# run on the build platform (I think).
|
||||||
|
#
|
||||||
|
# [0]: https://github.com/NixOS/nixpkgs/blob/612f97239e2cc474c13c9dafa0df378058c5ad8d/pkgs/build-support/rust/lib/default.nix#L64-L78
|
||||||
|
// (
|
||||||
|
let
|
||||||
|
inherit (pkgs.rust.lib) envVars;
|
||||||
|
in
|
||||||
|
pkgs.lib.optionalAttrs
|
||||||
|
(pkgs.stdenv.targetPlatform.rust.rustcTarget
|
||||||
|
!= pkgs.stdenv.hostPlatform.rust.rustcTarget)
|
||||||
|
(
|
||||||
|
let
|
||||||
|
inherit (pkgs.stdenv.targetPlatform.rust) cargoEnvVarTarget;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
|
||||||
|
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
|
||||||
|
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" =
|
||||||
|
envVars.linkerForTarget;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// (
|
||||||
|
let
|
||||||
|
inherit (pkgs.stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
|
||||||
|
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
|
||||||
|
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForHost;
|
||||||
|
CARGO_BUILD_TARGET = rustcTarget;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// (
|
||||||
|
let
|
||||||
|
inherit (pkgs.stdenv.buildPlatform.rust) cargoEnvVarTarget;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
|
||||||
|
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
|
||||||
|
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForBuild;
|
||||||
|
HOST_CC = "${pkgs.buildPackages.stdenv.cc}/bin/cc";
|
||||||
|
HOST_CXX = "${pkgs.buildPackages.stdenv.cc}/bin/c++";
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
package = pkgs: builder pkgs {
|
||||||
|
src = nix-filter {
|
||||||
|
root = ./.;
|
||||||
|
include = [
|
||||||
|
"src"
|
||||||
|
"Cargo.toml"
|
||||||
|
"Cargo.lock"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# This is redundant with CI
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
env = env pkgs;
|
||||||
|
nativeBuildInputs = nativeBuildInputs pkgs;
|
||||||
|
|
||||||
|
meta.mainProgram = cargoToml.package.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
mkOciImage = pkgs: package:
|
||||||
|
pkgs.dockerTools.buildImage {
|
||||||
|
name = package.pname;
|
||||||
|
tag = "next";
|
||||||
|
copyToRoot = [
|
||||||
|
pkgs.dockerTools.caCertificates
|
||||||
|
];
|
||||||
|
config = {
|
||||||
|
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
|
||||||
|
# are handled as expected
|
||||||
|
Entrypoint = [
|
||||||
|
"${pkgs.lib.getExe' pkgs.tini "tini"}"
|
||||||
|
"--"
|
||||||
|
];
|
||||||
|
Cmd = [
|
||||||
|
"${pkgs.lib.getExe package}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = {
|
||||||
|
default = package pkgsHost;
|
||||||
|
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
builtins.listToAttrs
|
||||||
|
(builtins.concatLists
|
||||||
|
(builtins.map
|
||||||
|
(crossSystem:
|
||||||
|
let
|
||||||
|
binaryName = "static-${crossSystem}";
|
||||||
|
pkgsCrossStatic =
|
||||||
|
(import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
crossSystem = {
|
||||||
|
config = crossSystem;
|
||||||
|
};
|
||||||
|
}).pkgsStatic;
|
||||||
|
in
|
||||||
|
[
|
||||||
|
# An output for a statically-linked binary
|
||||||
|
{
|
||||||
|
name = binaryName;
|
||||||
|
value = package pkgsCrossStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
# An output for an OCI image based on that binary
|
||||||
|
{
|
||||||
|
name = "oci-image-${crossSystem}";
|
||||||
|
value = mkOciImage
|
||||||
|
pkgsCrossStatic
|
||||||
|
self.packages.${system}.${binaryName};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
[
|
||||||
|
"x86_64-unknown-linux-musl"
|
||||||
|
"aarch64-unknown-linux-musl"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
devShells.default = pkgsHost.mkShell {
|
||||||
|
env = env pkgsHost // {
|
||||||
|
# Rust Analyzer needs to be able to find the path to default crate
|
||||||
|
# sources, and it can read this environment variable to do so. The
|
||||||
|
# `rust-src` component is required in order for this to work.
|
||||||
|
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Development tools
|
||||||
|
nativeBuildInputs = nativeBuildInputs pkgsHost ++ [
|
||||||
|
# Always use nightly rustfmt because most of its options are unstable
|
||||||
|
#
|
||||||
|
# This needs to come before `toolchain` in this list, otherwise
|
||||||
|
# `$PATH` will have stable rustfmt instead.
|
||||||
|
fenix.packages.${system}.latest.rustfmt
|
||||||
|
|
||||||
|
toolchain
|
||||||
|
] ++ (with pkgsHost; [
|
||||||
|
engage
|
||||||
|
|
||||||
|
# Needed for Complement
|
||||||
|
go
|
||||||
|
olm
|
||||||
|
|
||||||
|
# Needed for our script for Complement
|
||||||
|
jq
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,10 +118,19 @@ in
|
||||||
ssl = true;
|
ssl = true;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
addr = "[::]";
|
||||||
|
port = 443;
|
||||||
|
ssl = true;
|
||||||
|
} {
|
||||||
addr = "0.0.0.0";
|
addr = "0.0.0.0";
|
||||||
port = 8448;
|
port = 8448;
|
||||||
ssl = true;
|
ssl = true;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
addr = "[::]";
|
||||||
|
port = 8448;
|
||||||
|
ssl = true;
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
locations."/_matrix/" = {
|
locations."/_matrix/" = {
|
||||||
|
@ -170,7 +179,7 @@ in
|
||||||
upstreams = {
|
upstreams = {
|
||||||
"backend_conduit" = {
|
"backend_conduit" = {
|
||||||
servers = {
|
servers = {
|
||||||
"localhost:${toString config.services.matrix-conduit.settings.global.port}" = { };
|
"[::1]:${toString config.services.matrix-conduit.settings.global.port}" = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
22
rust-toolchain.toml
Normal file
22
rust-toolchain.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# This is the authoritiative configuration of this project's Rust toolchain.
|
||||||
|
#
|
||||||
|
# Other files that need upkeep when this changes:
|
||||||
|
#
|
||||||
|
# * `.gitlab-ci.yml`
|
||||||
|
# * `Cargo.toml`
|
||||||
|
# * `flake.nix`
|
||||||
|
#
|
||||||
|
# Search in those files for `rust-toolchain.toml` to find the relevant places.
|
||||||
|
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||||
|
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.75.0"
|
||||||
|
components = [
|
||||||
|
# For rust-analyzer
|
||||||
|
"rust-src",
|
||||||
|
]
|
||||||
|
targets = [
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
"x86_64-unknown-linux-musl",
|
||||||
|
"aarch64-unknown-linux-musl",
|
||||||
|
]
|
|
@ -18,7 +18,7 @@ where
|
||||||
let mut http_request = request
|
let mut http_request = request
|
||||||
.try_into_http_request::<BytesMut>(
|
.try_into_http_request::<BytesMut>(
|
||||||
destination,
|
destination,
|
||||||
SendAccessToken::IfRequired(""),
|
SendAccessToken::IfRequired(hs_token),
|
||||||
&[MatrixVersion::V1_0],
|
&[MatrixVersion::V1_0],
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -74,7 +74,10 @@ pub async fn get_register_available_route(
|
||||||
/// - Creates a new account and populates it with default account data
|
/// - Creates a new account and populates it with default account data
|
||||||
/// - If `inhibit_login` is false: Creates a device and returns device id and access_token
|
/// - If `inhibit_login` is false: Creates a device and returns device id and access_token
|
||||||
pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
|
pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
|
||||||
if !services().globals.allow_registration() && !body.from_appservice {
|
if !services().globals.allow_registration()
|
||||||
|
&& !body.from_appservice
|
||||||
|
&& services().globals.config.registration_token.is_none()
|
||||||
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
"Registration has been disabled.",
|
"Registration has been disabled.",
|
||||||
|
@ -121,7 +124,11 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
||||||
// UIAA
|
// UIAA
|
||||||
let mut uiaainfo = UiaaInfo {
|
let mut uiaainfo = UiaaInfo {
|
||||||
flows: vec![AuthFlow {
|
flows: vec![AuthFlow {
|
||||||
stages: vec![AuthType::Dummy],
|
stages: if services().globals.config.registration_token.is_some() {
|
||||||
|
vec![AuthType::RegistrationToken]
|
||||||
|
} else {
|
||||||
|
vec![AuthType::Dummy]
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
completed: Vec::new(),
|
completed: Vec::new(),
|
||||||
params: Default::default(),
|
params: Default::default(),
|
||||||
|
@ -222,11 +229,13 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
info!("New user {} registered on this server.", user_id);
|
info!("New user {} registered on this server.", user_id);
|
||||||
|
if !body.from_appservice && !is_guest {
|
||||||
services()
|
services()
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"New user {user_id} registered on this server."
|
"New user {user_id} registered on this server."
|
||||||
)));
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
// If this is the first real user, grant them admin privileges
|
// If this is the first real user, grant them admin privileges
|
||||||
// Note: the server user, @conduit:servername, is generated first
|
// Note: the server user, @conduit:servername, is generated first
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{services, Error, Result, Ruma};
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
|
@ -90,10 +91,10 @@ pub(crate) async fn get_alias_helper(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
return Ok(get_alias::v3::Response::new(
|
let mut servers = response.servers;
|
||||||
response.room_id,
|
servers.shuffle(&mut rand::thread_rng());
|
||||||
response.servers,
|
|
||||||
));
|
return Ok(get_alias::v3::Response::new(response.room_id, servers));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut room_id = None;
|
let mut room_id = None;
|
||||||
|
|
|
@ -75,7 +75,7 @@ pub async fn get_global_account_data_route(
|
||||||
|
|
||||||
let event: Box<RawJsonValue> = services()
|
let event: Box<RawJsonValue> = services()
|
||||||
.account_data
|
.account_data
|
||||||
.get(None, sender_user, body.event_type.clone().into())?
|
.get(None, sender_user, body.event_type.to_string().into())?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||||
|
|
||||||
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
|
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
|
||||||
|
@ -95,11 +95,7 @@ pub async fn get_room_account_data_route(
|
||||||
|
|
||||||
let event: Box<RawJsonValue> = services()
|
let event: Box<RawJsonValue> = services()
|
||||||
.account_data
|
.account_data
|
||||||
.get(
|
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
|
||||||
Some(&body.room_id),
|
|
||||||
sender_user,
|
|
||||||
body.event_type.clone().into(),
|
|
||||||
)?
|
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||||
|
|
||||||
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
|
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ruma::{
|
||||||
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
|
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
|
||||||
events::StateEventType,
|
events::StateEventType,
|
||||||
};
|
};
|
||||||
use std::{collections::HashSet, convert::TryFrom};
|
use std::collections::HashSet;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
||||||
|
@ -69,18 +69,16 @@ pub async fn get_context_route(
|
||||||
lazy_loaded.insert(base_event.sender.as_str().to_owned());
|
lazy_loaded.insert(base_event.sender.as_str().to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use limit with maximum 100
|
||||||
|
let limit = u64::from(body.limit).min(100) as usize;
|
||||||
|
|
||||||
let base_event = base_event.to_room_event();
|
let base_event = base_event.to_room_event();
|
||||||
|
|
||||||
let events_before: Vec<_> = services()
|
let events_before: Vec<_> = services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_until(sender_user, &room_id, base_token)?
|
.pdus_until(sender_user, &room_id, base_token)?
|
||||||
.take(
|
.take(limit / 2)
|
||||||
u32::try_from(body.limit).map_err(|_| {
|
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
|
||||||
})? as usize
|
|
||||||
/ 2,
|
|
||||||
)
|
|
||||||
.filter_map(|r| r.ok()) // Remove buggy events
|
.filter_map(|r| r.ok()) // Remove buggy events
|
||||||
.filter(|(_, pdu)| {
|
.filter(|(_, pdu)| {
|
||||||
services()
|
services()
|
||||||
|
@ -103,7 +101,10 @@ pub async fn get_context_route(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_token = events_before.last().map(|(count, _)| count.stringify());
|
let start_token = events_before
|
||||||
|
.last()
|
||||||
|
.map(|(count, _)| count.stringify())
|
||||||
|
.unwrap_or_else(|| base_token.stringify());
|
||||||
|
|
||||||
let events_before: Vec<_> = events_before
|
let events_before: Vec<_> = events_before
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -114,12 +115,7 @@ pub async fn get_context_route(
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_after(sender_user, &room_id, base_token)?
|
.pdus_after(sender_user, &room_id, base_token)?
|
||||||
.take(
|
.take(limit / 2)
|
||||||
u32::try_from(body.limit).map_err(|_| {
|
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
|
||||||
})? as usize
|
|
||||||
/ 2,
|
|
||||||
)
|
|
||||||
.filter_map(|r| r.ok()) // Remove buggy events
|
.filter_map(|r| r.ok()) // Remove buggy events
|
||||||
.filter(|(_, pdu)| {
|
.filter(|(_, pdu)| {
|
||||||
services()
|
services()
|
||||||
|
@ -161,7 +157,10 @@ pub async fn get_context_route(
|
||||||
.state_full_ids(shortstatehash)
|
.state_full_ids(shortstatehash)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let end_token = events_after.last().map(|(count, _)| count.stringify());
|
let end_token = events_after
|
||||||
|
.last()
|
||||||
|
.map(|(count, _)| count.stringify())
|
||||||
|
.unwrap_or_else(|| base_token.stringify());
|
||||||
|
|
||||||
let events_after: Vec<_> = events_after
|
let events_after: Vec<_> = events_after
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -198,8 +197,8 @@ pub async fn get_context_route(
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = get_context::v3::Response {
|
let resp = get_context::v3::Response {
|
||||||
start: start_token,
|
start: Some(start_token),
|
||||||
end: end_token,
|
end: Some(end_token),
|
||||||
events_before,
|
events_before,
|
||||||
event: Some(base_event),
|
event: Some(base_event),
|
||||||
events_after,
|
events_after,
|
||||||
|
|
|
@ -20,7 +20,6 @@ use ruma::{
|
||||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
name::RoomNameEventContent,
|
|
||||||
topic::RoomTopicEventContent,
|
topic::RoomTopicEventContent,
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
|
@ -203,17 +202,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||||
Error::bad_database("Invalid canonical alias event in database.")
|
Error::bad_database("Invalid canonical alias event in database.")
|
||||||
})
|
})
|
||||||
})?,
|
})?,
|
||||||
name: services()
|
name: services().rooms.state_accessor.get_name(&room_id)?,
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&room_id, &StateEventType::RoomName, "")?
|
|
||||||
.map_or(Ok(None), |s| {
|
|
||||||
serde_json::from_str(s.content.get())
|
|
||||||
.map(|c: RoomNameEventContent| c.name)
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database("Invalid room name event in database.")
|
|
||||||
})
|
|
||||||
})?,
|
|
||||||
num_joined_members: services()
|
num_joined_members: services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
|
@ -232,6 +221,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||||
serde_json::from_str(s.content.get())
|
serde_json::from_str(s.content.get())
|
||||||
.map(|c: RoomTopicEventContent| Some(c.topic))
|
.map(|c: RoomTopicEventContent| Some(c.topic))
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
|
error!("Invalid room topic event in database for room {}", room_id);
|
||||||
Error::bad_database("Invalid room topic event in database.")
|
Error::bad_database("Invalid room topic event in database.")
|
||||||
})
|
})
|
||||||
})?,
|
})?,
|
||||||
|
|
|
@ -17,7 +17,11 @@ use ruma::{
|
||||||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::{
|
||||||
|
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/keys/upload`
|
/// # `POST /_matrix/client/r0/keys/upload`
|
||||||
///
|
///
|
||||||
|
@ -132,6 +136,7 @@ pub async fn upload_signing_keys_route(
|
||||||
master_key,
|
master_key,
|
||||||
&body.self_signing_key,
|
&body.self_signing_key,
|
||||||
&body.user_signing_key,
|
&body.user_signing_key,
|
||||||
|
true, // notify so that other users see the new keys
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,18 +156,6 @@ pub async fn upload_signatures_route(
|
||||||
let key = serde_json::to_value(key)
|
let key = serde_json::to_value(key)
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid key JSON"))?;
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid key JSON"))?;
|
||||||
|
|
||||||
let is_signed_key = match key.get("usage") {
|
|
||||||
Some(usage) => usage
|
|
||||||
.as_array()
|
|
||||||
.map(|usage| !usage.contains(&json!("master")))
|
|
||||||
.unwrap_or(false),
|
|
||||||
None => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_signed_key {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for signature in key
|
for signature in key
|
||||||
.get("signatures")
|
.get("signatures")
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or(Error::BadRequest(
|
||||||
|
@ -323,15 +316,17 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(master_key) = services()
|
if let Some(master_key) =
|
||||||
|
services()
|
||||||
.users
|
.users
|
||||||
.get_master_key(user_id, &allowed_signatures)?
|
.get_master_key(sender_user, user_id, &allowed_signatures)?
|
||||||
{
|
{
|
||||||
master_keys.insert(user_id.to_owned(), master_key);
|
master_keys.insert(user_id.to_owned(), master_key);
|
||||||
}
|
}
|
||||||
if let Some(self_signing_key) = services()
|
if let Some(self_signing_key) =
|
||||||
|
services()
|
||||||
.users
|
.users
|
||||||
.get_self_signing_key(user_id, &allowed_signatures)?
|
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
|
||||||
{
|
{
|
||||||
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
||||||
}
|
}
|
||||||
|
@ -344,36 +339,96 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
||||||
|
|
||||||
let mut failures = BTreeMap::new();
|
let mut failures = BTreeMap::new();
|
||||||
|
|
||||||
|
let back_off = |id| match services()
|
||||||
|
.globals
|
||||||
|
.bad_query_ratelimiter
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry(id)
|
||||||
|
{
|
||||||
|
hash_map::Entry::Vacant(e) => {
|
||||||
|
e.insert((Instant::now(), 1));
|
||||||
|
}
|
||||||
|
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||||
|
};
|
||||||
|
|
||||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(server, vec)| async move {
|
.map(|(server, vec)| async move {
|
||||||
|
if let Some((time, tries)) = services()
|
||||||
|
.globals
|
||||||
|
.bad_query_ratelimiter
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(server)
|
||||||
|
{
|
||||||
|
// 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 query from {:?}", server);
|
||||||
|
return (
|
||||||
|
server,
|
||||||
|
Err(Error::BadServerResponse("bad query, still backing off")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut device_keys_input_fed = BTreeMap::new();
|
let mut device_keys_input_fed = BTreeMap::new();
|
||||||
for (user_id, keys) in vec {
|
for (user_id, keys) in vec {
|
||||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
server,
|
server,
|
||||||
services()
|
tokio::time::timeout(
|
||||||
.sending
|
Duration::from_secs(25),
|
||||||
.send_federation_request(
|
services().sending.send_federation_request(
|
||||||
server,
|
server,
|
||||||
federation::keys::get_keys::v1::Request {
|
federation::keys::get_keys::v1::Request {
|
||||||
device_keys: device_keys_input_fed,
|
device_keys: device_keys_input_fed,
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.await,
|
.await
|
||||||
|
.map_err(|_e| Error::BadServerResponse("Query took too long")),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
while let Some((server, response)) = futures.next().await {
|
while let Some((server, response)) = futures.next().await {
|
||||||
match response {
|
match response {
|
||||||
Ok(response) => {
|
Ok(Ok(response)) => {
|
||||||
master_keys.extend(response.master_keys);
|
for (user, masterkey) in response.master_keys {
|
||||||
|
let (master_key_id, mut master_key) =
|
||||||
|
services().users.parse_master_key(&user, &masterkey)?;
|
||||||
|
|
||||||
|
if let Some(our_master_key) = services().users.get_key(
|
||||||
|
&master_key_id,
|
||||||
|
sender_user,
|
||||||
|
&user,
|
||||||
|
&allowed_signatures,
|
||||||
|
)? {
|
||||||
|
let (_, our_master_key) =
|
||||||
|
services().users.parse_master_key(&user, &our_master_key)?;
|
||||||
|
master_key.signatures.extend(our_master_key.signatures);
|
||||||
|
}
|
||||||
|
let json = serde_json::to_value(master_key).expect("to_value always works");
|
||||||
|
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
|
||||||
|
services().users.add_cross_signing_keys(
|
||||||
|
&user, &raw, &None, &None,
|
||||||
|
false, // Dont notify. A notification would trigger another key request resulting in an endless loop
|
||||||
|
)?;
|
||||||
|
master_keys.insert(user, raw);
|
||||||
|
}
|
||||||
|
|
||||||
self_signing_keys.extend(response.self_signing_keys);
|
self_signing_keys.extend(response.self_signing_keys);
|
||||||
device_keys.extend(response.device_keys);
|
device_keys.extend(response.device_keys);
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
_ => {
|
||||||
|
back_off(server.to_owned());
|
||||||
failures.insert(server.to_string(), json!({}));
|
failures.insert(server.to_string(), json!({}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
|
@ -49,7 +51,7 @@ pub async fn create_content_route(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(create_content::v3::Response {
|
Ok(create_content::v3::Response {
|
||||||
content_uri: mxc.try_into().expect("Invalid mxc:// URI"),
|
content_uri: mxc.into(),
|
||||||
blurhash: None,
|
blurhash: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -67,6 +69,8 @@ pub async fn get_remote_content(
|
||||||
allow_remote: false,
|
allow_remote: false,
|
||||||
server_name: server_name.to_owned(),
|
server_name: server_name.to_owned(),
|
||||||
media_id,
|
media_id,
|
||||||
|
timeout_ms: Duration::from_secs(20),
|
||||||
|
allow_redirect: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -194,6 +198,8 @@ pub async fn get_content_thumbnail_route(
|
||||||
method: body.method.clone(),
|
method: body.method.clone(),
|
||||||
server_name: body.server_name.clone(),
|
server_name: body.server_name.clone(),
|
||||||
media_id: body.media_id.clone(),
|
media_id: body.media_id.clone(),
|
||||||
|
timeout_ms: Duration::from_secs(20),
|
||||||
|
allow_redirect: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -17,7 +17,7 @@ use ruma::{
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
},
|
},
|
||||||
RoomEventType, StateEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
serde::Base64,
|
serde::Base64,
|
||||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||||
|
@ -64,7 +64,12 @@ pub async fn join_room_by_id_route(
|
||||||
.map(|user| user.server_name().to_owned()),
|
.map(|user| user.server_name().to_owned()),
|
||||||
);
|
);
|
||||||
|
|
||||||
servers.push(body.room_id.server_name().to_owned());
|
servers.push(
|
||||||
|
body.room_id
|
||||||
|
.server_name()
|
||||||
|
.expect("Room IDs should always have a server name")
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
join_room_by_id_helper(
|
join_room_by_id_helper(
|
||||||
body.sender_user.as_deref(),
|
body.sender_user.as_deref(),
|
||||||
|
@ -105,13 +110,19 @@ pub async fn join_room_by_id_or_alias_route(
|
||||||
.map(|user| user.server_name().to_owned()),
|
.map(|user| user.server_name().to_owned()),
|
||||||
);
|
);
|
||||||
|
|
||||||
servers.push(room_id.server_name().to_owned());
|
servers.push(
|
||||||
|
room_id
|
||||||
|
.server_name()
|
||||||
|
.expect("Room IDs should always have a server name")
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
(servers, room_id)
|
(servers, room_id)
|
||||||
}
|
}
|
||||||
Err(room_alias) => {
|
Err(room_alias) => {
|
||||||
let response = get_alias_helper(room_alias).await?;
|
let response = get_alias_helper(room_alias).await?;
|
||||||
|
|
||||||
(response.servers.into_iter().collect(), response.room_id)
|
(response.servers, response.room_id)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,7 +220,7 @@ pub async fn kick_user_route(
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(body.user_id.to_string()),
|
state_key: Some(body.user_id.to_string()),
|
||||||
|
@ -273,7 +284,7 @@ pub async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_use
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(body.user_id.to_string()),
|
state_key: Some(body.user_id.to_string()),
|
||||||
|
@ -331,7 +342,7 @@ pub async fn unban_user_route(
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(body.user_id.to_string()),
|
state_key: Some(body.user_id.to_string()),
|
||||||
|
@ -399,7 +410,7 @@ pub async fn get_member_events_route(
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -434,7 +445,7 @@ pub async fn joined_members_route(
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -590,6 +601,7 @@ async fn join_room_by_id_helper(
|
||||||
room_id: room_id.to_owned(),
|
room_id: room_id.to_owned(),
|
||||||
event_id: event_id.to_owned(),
|
event_id: event_id.to_owned(),
|
||||||
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
||||||
|
omit_members: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -597,7 +609,7 @@ async fn join_room_by_id_helper(
|
||||||
info!("send_join finished");
|
info!("send_join finished");
|
||||||
|
|
||||||
if let Some(signed_raw) = &send_join_response.room_state.event {
|
if let Some(signed_raw) = &send_join_response.room_state.event {
|
||||||
info!("There is a signed event. This room is probably using restricted joins");
|
info!("There is a signed event. This room is probably using restricted joins. Adding signature to our event");
|
||||||
let (signed_event_id, signed_value) =
|
let (signed_event_id, signed_value) =
|
||||||
match gen_event_id_canonical_json(signed_raw, &room_version_id) {
|
match gen_event_id_canonical_json(signed_raw, &room_version_id) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
|
@ -617,7 +629,7 @@ async fn join_room_by_id_helper(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(signature) = signed_value["signatures"]
|
match signed_value["signatures"]
|
||||||
.as_object()
|
.as_object()
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or(Error::BadRequest(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
|
@ -628,20 +640,22 @@ async fn join_room_by_id_helper(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
"Server did not send its signature",
|
"Server did not send its signature",
|
||||||
))
|
))
|
||||||
})
|
}) {
|
||||||
{
|
Ok(signature) => {
|
||||||
join_event
|
join_event
|
||||||
.get_mut("signatures")
|
.get_mut("signatures")
|
||||||
.expect("we created a valid pdu")
|
.expect("we created a valid pdu")
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.expect("we created a valid pdu")
|
.expect("we created a valid pdu")
|
||||||
.insert(remote_server.to_string(), signature.clone());
|
.insert(remote_server.to_string(), signature.clone());
|
||||||
} else {
|
}
|
||||||
|
Err(e) => {
|
||||||
warn!(
|
warn!(
|
||||||
"Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}",
|
"Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}: {e:?}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
services().rooms.short.get_or_create_shortroomid(room_id)?;
|
services().rooms.short.get_or_create_shortroomid(room_id)?;
|
||||||
|
|
||||||
|
@ -672,7 +686,7 @@ async fn join_room_by_id_helper(
|
||||||
};
|
};
|
||||||
|
|
||||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||||
warn!("{:?}: {}", value, e);
|
warn!("Invalid PDU in send_join response: {} {:?}", e, value);
|
||||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
Error::BadServerResponse("Invalid PDU in send_join response.")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -708,7 +722,7 @@ async fn join_room_by_id_helper(
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Running send_join auth check");
|
info!("Running send_join auth check");
|
||||||
if !state_res::event_auth::auth_check(
|
let authenticated = state_res::event_auth::auth_check(
|
||||||
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
|
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
|
||||||
&parsed_join_pdu,
|
&parsed_join_pdu,
|
||||||
None::<PduEvent>, // TODO: third party invite
|
None::<PduEvent>, // TODO: third party invite
|
||||||
|
@ -731,7 +745,9 @@ async fn join_room_by_id_helper(
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!("Auth check failed: {e}");
|
warn!("Auth check failed: {e}");
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed")
|
Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed")
|
||||||
})? {
|
})?;
|
||||||
|
|
||||||
|
if !authenticated {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
"Auth check failed",
|
"Auth check failed",
|
||||||
|
@ -741,6 +757,7 @@ async fn join_room_by_id_helper(
|
||||||
info!("Saving state from send_join");
|
info!("Saving state from send_join");
|
||||||
let (statehash_before_join, new, removed) = services().rooms.state_compressor.save_state(
|
let (statehash_before_join, new, removed) = services().rooms.state_compressor.save_state(
|
||||||
room_id,
|
room_id,
|
||||||
|
Arc::new(
|
||||||
state
|
state
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, id)| {
|
.map(|(k, id)| {
|
||||||
|
@ -750,6 +767,7 @@ async fn join_room_by_id_helper(
|
||||||
.compress_state_event(k, &id)
|
.compress_state_event(k, &id)
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
services()
|
services()
|
||||||
|
@ -886,7 +904,7 @@ async fn join_room_by_id_helper(
|
||||||
// Try normal join first
|
// Try normal join first
|
||||||
let error = match services().rooms.timeline.build_and_append_pdu(
|
let error = match services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(sender_user.to_string()),
|
state_key: Some(sender_user.to_string()),
|
||||||
|
@ -900,7 +918,13 @@ async fn join_room_by_id_helper(
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !restriction_rooms.is_empty() {
|
if !restriction_rooms.is_empty()
|
||||||
|
&& servers
|
||||||
|
.iter()
|
||||||
|
.filter(|s| *s != services().globals.server_name())
|
||||||
|
.count()
|
||||||
|
> 0
|
||||||
|
{
|
||||||
info!(
|
info!(
|
||||||
"We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements"
|
"We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements"
|
||||||
);
|
);
|
||||||
|
@ -996,6 +1020,7 @@ async fn join_room_by_id_helper(
|
||||||
room_id: room_id.to_owned(),
|
room_id: room_id.to_owned(),
|
||||||
event_id: event_id.to_owned(),
|
event_id: event_id.to_owned(),
|
||||||
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
||||||
|
omit_members: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1186,7 +1211,7 @@ pub(crate) async fn invite_helper<'a>(
|
||||||
|
|
||||||
let (pdu, pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
let (pdu, pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content,
|
content,
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(user_id.to_string()),
|
state_key: Some(user_id.to_string()),
|
||||||
|
@ -1295,7 +1320,7 @@ pub(crate) async fn invite_helper<'a>(
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Invite,
|
membership: MembershipState::Invite,
|
||||||
displayname: services().users.displayname(user_id)?,
|
displayname: services().users.displayname(user_id)?,
|
||||||
|
@ -1351,7 +1376,7 @@ pub async fn leave_all_rooms(user_id: &UserId) -> Result<()> {
|
||||||
pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
|
pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
|
||||||
// Ask a remote server if we don't have this room
|
// Ask a remote server if we don't have this room
|
||||||
if !services().rooms.metadata.exists(room_id)?
|
if !services().rooms.metadata.exists(room_id)?
|
||||||
&& room_id.server_name() != services().globals.server_name()
|
&& room_id.server_name() != Some(services().globals.server_name())
|
||||||
{
|
{
|
||||||
if let Err(e) = remote_leave_room(user_id, room_id).await {
|
if let Err(e) = remote_leave_room(user_id, room_id).await {
|
||||||
warn!("Failed to leave room {} remotely: {}", user_id, e);
|
warn!("Failed to leave room {} remotely: {}", user_id, e);
|
||||||
|
@ -1420,7 +1445,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(user_id.to_string()),
|
state_key: Some(user_id.to_string()),
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ruma::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
message::{get_message_events, send_message_event},
|
message::{get_message_events, send_message_event},
|
||||||
},
|
},
|
||||||
events::{RoomEventType, StateEventType},
|
events::{StateEventType, TimelineEventType},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
|
@ -39,7 +39,7 @@ pub async fn send_message_event_route(
|
||||||
let state_lock = mutex_state.lock().await;
|
let state_lock = mutex_state.lock().await;
|
||||||
|
|
||||||
// Forbid m.room.encrypted if encryption is disabled
|
// Forbid m.room.encrypted if encryption is disabled
|
||||||
if RoomEventType::RoomEncrypted == body.event_type.to_string().into()
|
if TimelineEventType::RoomEncrypted == body.event_type.to_string().into()
|
||||||
&& !services().globals.allow_encryption()
|
&& !services().globals.allow_encryption()
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
|
@ -116,15 +116,15 @@ pub async fn get_message_events_route(
|
||||||
let from = match body.from.clone() {
|
let from = match body.from.clone() {
|
||||||
Some(from) => PduCount::try_from_string(&from)?,
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
None => match body.dir {
|
None => match body.dir {
|
||||||
ruma::api::client::Direction::Forward => PduCount::min(),
|
ruma::api::Direction::Forward => PduCount::min(),
|
||||||
ruma::api::client::Direction::Backward => PduCount::max(),
|
ruma::api::Direction::Backward => PduCount::max(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let to = body
|
let to = body
|
||||||
.to
|
.to
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|t| PduCount::try_from_string(&t).ok());
|
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
services().rooms.lazy_loading.lazy_load_confirm_delivery(
|
services().rooms.lazy_loading.lazy_load_confirm_delivery(
|
||||||
sender_user,
|
sender_user,
|
||||||
|
@ -133,8 +133,7 @@ pub async fn get_message_events_route(
|
||||||
from,
|
from,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Use limit or else 10
|
let limit = u64::from(body.limit).min(100) as usize;
|
||||||
let limit = body.limit.try_into().map_or(10_usize, |l: u32| l as usize);
|
|
||||||
|
|
||||||
let next_token;
|
let next_token;
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ pub async fn get_message_events_route(
|
||||||
let mut lazy_loaded = HashSet::new();
|
let mut lazy_loaded = HashSet::new();
|
||||||
|
|
||||||
match body.dir {
|
match body.dir {
|
||||||
ruma::api::client::Direction::Forward => {
|
ruma::api::Direction::Forward => {
|
||||||
let events_after: Vec<_> = services()
|
let events_after: Vec<_> = services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
|
@ -187,7 +186,7 @@ pub async fn get_message_events_route(
|
||||||
resp.end = next_token.map(|count| count.stringify());
|
resp.end = next_token.map(|count| count.stringify());
|
||||||
resp.chunk = events_after;
|
resp.chunk = events_after;
|
||||||
}
|
}
|
||||||
ruma::api::client::Direction::Backward => {
|
ruma::api::Direction::Backward => {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
|
|
|
@ -16,14 +16,17 @@ mod profile;
|
||||||
mod push;
|
mod push;
|
||||||
mod read_marker;
|
mod read_marker;
|
||||||
mod redact;
|
mod redact;
|
||||||
|
mod relations;
|
||||||
mod report;
|
mod report;
|
||||||
mod room;
|
mod room;
|
||||||
mod search;
|
mod search;
|
||||||
mod session;
|
mod session;
|
||||||
|
mod space;
|
||||||
mod state;
|
mod state;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod thirdparty;
|
mod thirdparty;
|
||||||
|
mod threads;
|
||||||
mod to_device;
|
mod to_device;
|
||||||
mod typing;
|
mod typing;
|
||||||
mod unversioned;
|
mod unversioned;
|
||||||
|
@ -48,14 +51,17 @@ pub use profile::*;
|
||||||
pub use push::*;
|
pub use push::*;
|
||||||
pub use read_marker::*;
|
pub use read_marker::*;
|
||||||
pub use redact::*;
|
pub use redact::*;
|
||||||
|
pub use relations::*;
|
||||||
pub use report::*;
|
pub use report::*;
|
||||||
pub use room::*;
|
pub use room::*;
|
||||||
pub use search::*;
|
pub use search::*;
|
||||||
pub use session::*;
|
pub use session::*;
|
||||||
|
pub use space::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use sync::*;
|
pub use sync::*;
|
||||||
pub use tag::*;
|
pub use tag::*;
|
||||||
pub use thirdparty::*;
|
pub use thirdparty::*;
|
||||||
|
pub use threads::*;
|
||||||
pub use to_device::*;
|
pub use to_device::*;
|
||||||
pub use typing::*;
|
pub use typing::*;
|
||||||
pub use unversioned::*;
|
pub use unversioned::*;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::{services, utils, Result, Ruma};
|
use crate::{services, utils, Error, Result, Ruma};
|
||||||
use ruma::api::client::presence::{get_presence, set_presence};
|
use ruma::api::client::{
|
||||||
|
error::ErrorKind,
|
||||||
|
presence::{get_presence, set_presence},
|
||||||
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
||||||
|
@ -79,6 +82,9 @@ pub async fn get_presence_route(
|
||||||
presence: presence.content.presence,
|
presence: presence.content.presence,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
todo!();
|
Err(Error::BadRequest(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
"Presence state for this user was not found",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ruma::{
|
||||||
},
|
},
|
||||||
federation::{self, query::get_profile_information::v1::ProfileField},
|
federation::{self, query::get_profile_information::v1::ProfileField},
|
||||||
},
|
},
|
||||||
events::{room::member::RoomMemberEventContent, RoomEventType, StateEventType},
|
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -37,7 +37,7 @@ pub async fn set_displayname_route(
|
||||||
.map(|room_id| {
|
.map(|room_id| {
|
||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
displayname: body.displayname.clone(),
|
displayname: body.displayname.clone(),
|
||||||
..serde_json::from_str(
|
..serde_json::from_str(
|
||||||
|
@ -172,7 +172,7 @@ pub async fn set_avatar_url_route(
|
||||||
.map(|room_id| {
|
.map(|room_id| {
|
||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
avatar_url: body.avatar_url.clone(),
|
avatar_url: body.avatar_url.clone(),
|
||||||
..serde_json::from_str(
|
..serde_json::from_str(
|
||||||
|
|
|
@ -5,11 +5,11 @@ use ruma::{
|
||||||
push::{
|
push::{
|
||||||
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
|
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
|
||||||
get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
|
get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
|
||||||
set_pushrule_enabled, RuleKind, RuleScope,
|
set_pushrule_enabled, RuleScope,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
events::{push_rules::PushRulesEvent, GlobalAccountDataEventType},
|
events::{push_rules::PushRulesEvent, GlobalAccountDataEventType},
|
||||||
push::{ConditionalPushRuleInit, NewPushRule, PatternedPushRuleInit, SimplePushRuleInit},
|
push::{InsertPushRuleError, RemovePushRuleError},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/pushrules`
|
/// # `GET /_matrix/client/r0/pushrules`
|
||||||
|
@ -65,30 +65,10 @@ pub async fn get_pushrule_route(
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
||||||
.content;
|
.content;
|
||||||
|
|
||||||
let global = account_data.global;
|
let rule = account_data
|
||||||
let rule = match body.kind {
|
.global
|
||||||
RuleKind::Override => global
|
.get(body.kind.clone(), &body.rule_id)
|
||||||
.override_
|
.map(Into::into);
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.clone().into()),
|
|
||||||
RuleKind::Underride => global
|
|
||||||
.underride
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.clone().into()),
|
|
||||||
RuleKind::Sender => global
|
|
||||||
.sender
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.clone().into()),
|
|
||||||
RuleKind::Room => global
|
|
||||||
.room
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.clone().into()),
|
|
||||||
RuleKind::Content => global
|
|
||||||
.content
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.clone().into()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(rule) = rule {
|
if let Some(rule) = rule {
|
||||||
Ok(get_pushrule::v3::Response { rule })
|
Ok(get_pushrule::v3::Response { rule })
|
||||||
|
@ -131,66 +111,36 @@ pub async fn set_pushrule_route(
|
||||||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||||
|
|
||||||
let global = &mut account_data.content.global;
|
if let Err(error) = account_data.content.global.insert(
|
||||||
match body.rule {
|
body.rule.clone(),
|
||||||
NewPushRule::Override(rule) => {
|
body.after.as_deref(),
|
||||||
global.override_.replace(
|
body.before.as_deref(),
|
||||||
ConditionalPushRuleInit {
|
) {
|
||||||
actions: rule.actions,
|
let err = match error {
|
||||||
default: false,
|
InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
|
||||||
enabled: true,
|
ErrorKind::InvalidParam,
|
||||||
rule_id: rule.rule_id,
|
"Rule IDs starting with a dot are reserved for server-default rules.",
|
||||||
conditions: rule.conditions,
|
),
|
||||||
}
|
InsertPushRuleError::InvalidRuleId => Error::BadRequest(
|
||||||
.into(),
|
ErrorKind::InvalidParam,
|
||||||
);
|
"Rule ID containing invalid characters.",
|
||||||
}
|
),
|
||||||
NewPushRule::Underride(rule) => {
|
InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
|
||||||
global.underride.replace(
|
ErrorKind::InvalidParam,
|
||||||
ConditionalPushRuleInit {
|
"Can't place a push rule relatively to a server-default rule.",
|
||||||
actions: rule.actions,
|
),
|
||||||
default: false,
|
InsertPushRuleError::UnknownRuleId => Error::BadRequest(
|
||||||
enabled: true,
|
ErrorKind::NotFound,
|
||||||
rule_id: rule.rule_id,
|
"The before or after rule could not be found.",
|
||||||
conditions: rule.conditions,
|
),
|
||||||
}
|
InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest(
|
||||||
.into(),
|
ErrorKind::InvalidParam,
|
||||||
);
|
"The before rule has a higher priority than the after rule.",
|
||||||
}
|
),
|
||||||
NewPushRule::Sender(rule) => {
|
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||||
global.sender.replace(
|
};
|
||||||
SimplePushRuleInit {
|
|
||||||
actions: rule.actions,
|
return Err(err);
|
||||||
default: false,
|
|
||||||
enabled: true,
|
|
||||||
rule_id: rule.rule_id,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
NewPushRule::Room(rule) => {
|
|
||||||
global.room.replace(
|
|
||||||
SimplePushRuleInit {
|
|
||||||
actions: rule.actions,
|
|
||||||
default: false,
|
|
||||||
enabled: true,
|
|
||||||
rule_id: rule.rule_id,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
NewPushRule::Content(rule) => {
|
|
||||||
global.content.replace(
|
|
||||||
PatternedPushRuleInit {
|
|
||||||
actions: rule.actions,
|
|
||||||
default: false,
|
|
||||||
enabled: true,
|
|
||||||
rule_id: rule.rule_id,
|
|
||||||
pattern: rule.pattern,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services().account_data.update(
|
services().account_data.update(
|
||||||
|
@ -235,33 +185,15 @@ pub async fn get_pushrule_actions_route(
|
||||||
.content;
|
.content;
|
||||||
|
|
||||||
let global = account_data.global;
|
let global = account_data.global;
|
||||||
let actions = match body.kind {
|
let actions = global
|
||||||
RuleKind::Override => global
|
.get(body.kind.clone(), &body.rule_id)
|
||||||
.override_
|
.map(|rule| rule.actions().to_owned())
|
||||||
.get(body.rule_id.as_str())
|
.ok_or(Error::BadRequest(
|
||||||
.map(|rule| rule.actions.clone()),
|
ErrorKind::NotFound,
|
||||||
RuleKind::Underride => global
|
"Push rule not found.",
|
||||||
.underride
|
))?;
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.actions.clone()),
|
|
||||||
RuleKind::Sender => global
|
|
||||||
.sender
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.actions.clone()),
|
|
||||||
RuleKind::Room => global
|
|
||||||
.room
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.actions.clone()),
|
|
||||||
RuleKind::Content => global
|
|
||||||
.content
|
|
||||||
.get(body.rule_id.as_str())
|
|
||||||
.map(|rule| rule.actions.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(get_pushrule_actions::v3::Response {
|
Ok(get_pushrule_actions::v3::Response { actions })
|
||||||
actions: actions.unwrap_or_default(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
|
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
|
||||||
|
@ -294,40 +226,17 @@ pub async fn set_pushrule_actions_route(
|
||||||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||||
|
|
||||||
let global = &mut account_data.content.global;
|
if account_data
|
||||||
match body.kind {
|
.content
|
||||||
RuleKind::Override => {
|
.global
|
||||||
if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone())
|
||||||
rule.actions = body.actions.clone();
|
.is_err()
|
||||||
global.override_.replace(rule);
|
{
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
"Push rule not found.",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
RuleKind::Underride => {
|
|
||||||
if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
|
||||||
rule.actions = body.actions.clone();
|
|
||||||
global.underride.replace(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Sender => {
|
|
||||||
if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
|
||||||
rule.actions = body.actions.clone();
|
|
||||||
global.sender.replace(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Room => {
|
|
||||||
if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
|
||||||
rule.actions = body.actions.clone();
|
|
||||||
global.room.replace(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Content => {
|
|
||||||
if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
|
||||||
rule.actions = body.actions.clone();
|
|
||||||
global.content.replace(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
services().account_data.update(
|
services().account_data.update(
|
||||||
None,
|
None,
|
||||||
|
@ -370,34 +279,13 @@ pub async fn get_pushrule_enabled_route(
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||||
|
|
||||||
let global = account_data.content.global;
|
let global = account_data.content.global;
|
||||||
let enabled = match body.kind {
|
let enabled = global
|
||||||
RuleKind::Override => global
|
.get(body.kind.clone(), &body.rule_id)
|
||||||
.override_
|
.map(|r| r.enabled())
|
||||||
.iter()
|
.ok_or(Error::BadRequest(
|
||||||
.find(|rule| rule.rule_id == body.rule_id)
|
ErrorKind::NotFound,
|
||||||
.map_or(false, |rule| rule.enabled),
|
"Push rule not found.",
|
||||||
RuleKind::Underride => global
|
))?;
|
||||||
.underride
|
|
||||||
.iter()
|
|
||||||
.find(|rule| rule.rule_id == body.rule_id)
|
|
||||||
.map_or(false, |rule| rule.enabled),
|
|
||||||
RuleKind::Sender => global
|
|
||||||
.sender
|
|
||||||
.iter()
|
|
||||||
.find(|rule| rule.rule_id == body.rule_id)
|
|
||||||
.map_or(false, |rule| rule.enabled),
|
|
||||||
RuleKind::Room => global
|
|
||||||
.room
|
|
||||||
.iter()
|
|
||||||
.find(|rule| rule.rule_id == body.rule_id)
|
|
||||||
.map_or(false, |rule| rule.enabled),
|
|
||||||
RuleKind::Content => global
|
|
||||||
.content
|
|
||||||
.iter()
|
|
||||||
.find(|rule| rule.rule_id == body.rule_id)
|
|
||||||
.map_or(false, |rule| rule.enabled),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(get_pushrule_enabled::v3::Response { enabled })
|
Ok(get_pushrule_enabled::v3::Response { enabled })
|
||||||
}
|
}
|
||||||
|
@ -432,44 +320,16 @@ pub async fn set_pushrule_enabled_route(
|
||||||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||||
|
|
||||||
let global = &mut account_data.content.global;
|
if account_data
|
||||||
match body.kind {
|
.content
|
||||||
RuleKind::Override => {
|
.global
|
||||||
if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
.set_enabled(body.kind.clone(), &body.rule_id, body.enabled)
|
||||||
global.override_.remove(&rule);
|
.is_err()
|
||||||
rule.enabled = body.enabled;
|
{
|
||||||
global.override_.insert(rule);
|
return Err(Error::BadRequest(
|
||||||
}
|
ErrorKind::NotFound,
|
||||||
}
|
"Push rule not found.",
|
||||||
RuleKind::Underride => {
|
));
|
||||||
if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.underride.remove(&rule);
|
|
||||||
rule.enabled = body.enabled;
|
|
||||||
global.underride.insert(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Sender => {
|
|
||||||
if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.sender.remove(&rule);
|
|
||||||
rule.enabled = body.enabled;
|
|
||||||
global.sender.insert(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Room => {
|
|
||||||
if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.room.remove(&rule);
|
|
||||||
rule.enabled = body.enabled;
|
|
||||||
global.room.insert(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Content => {
|
|
||||||
if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.content.remove(&rule);
|
|
||||||
rule.enabled = body.enabled;
|
|
||||||
global.content.insert(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services().account_data.update(
|
services().account_data.update(
|
||||||
|
@ -512,34 +372,23 @@ pub async fn delete_pushrule_route(
|
||||||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||||
|
|
||||||
let global = &mut account_data.content.global;
|
if let Err(error) = account_data
|
||||||
match body.kind {
|
.content
|
||||||
RuleKind::Override => {
|
.global
|
||||||
if let Some(rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
.remove(body.kind.clone(), &body.rule_id)
|
||||||
global.override_.remove(&rule);
|
{
|
||||||
|
let err = match error {
|
||||||
|
RemovePushRuleError::ServerDefault => Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Cannot delete a server-default pushrule.",
|
||||||
|
),
|
||||||
|
RemovePushRuleError::NotFound => {
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")
|
||||||
}
|
}
|
||||||
}
|
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||||
RuleKind::Underride => {
|
};
|
||||||
if let Some(rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.underride.remove(&rule);
|
return Err(err);
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Sender => {
|
|
||||||
if let Some(rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.sender.remove(&rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Room => {
|
|
||||||
if let Some(rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.room.remove(&rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuleKind::Content => {
|
|
||||||
if let Some(rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
|
||||||
global.content.remove(&rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services().account_data.update(
|
services().account_data.update(
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use crate::{service::pdu::PduBuilder, services, Result, Ruma};
|
use crate::{service::pdu::PduBuilder, services, Result, Ruma};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::redact::redact_event,
|
api::client::redact::redact_event,
|
||||||
events::{room::redaction::RoomRedactionEventContent, RoomEventType},
|
events::{room::redaction::RoomRedactionEventContent, TimelineEventType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
@ -32,8 +32,9 @@ pub async fn redact_event_route(
|
||||||
|
|
||||||
let event_id = services().rooms.timeline.build_and_append_pdu(
|
let event_id = services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomRedaction,
|
event_type: TimelineEventType::RoomRedaction,
|
||||||
content: to_raw_value(&RoomRedactionEventContent {
|
content: to_raw_value(&RoomRedactionEventContent {
|
||||||
|
redacts: Some(body.event_id.clone()),
|
||||||
reason: body.reason.clone(),
|
reason: body.reason.clone(),
|
||||||
})
|
})
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
|
|
146
src/api/client_server/relations.rs
Normal file
146
src/api/client_server/relations.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use ruma::api::client::relations::{
|
||||||
|
get_relating_events, get_relating_events_with_rel_type,
|
||||||
|
get_relating_events_with_rel_type_and_event_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{service::rooms::timeline::PduCount, services, Result, Ruma};
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
|
||||||
|
pub async fn get_relating_events_with_rel_type_and_event_type_route(
|
||||||
|
body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
|
||||||
|
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
let from = match body.from.clone() {
|
||||||
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
|
None => match ruma::api::Direction::Backward {
|
||||||
|
// TODO: fix ruma so `body.dir` exists
|
||||||
|
ruma::api::Direction::Forward => PduCount::min(),
|
||||||
|
ruma::api::Direction::Backward => PduCount::max(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let to = body
|
||||||
|
.to
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
|
// Use limit or else 10, with maximum 100
|
||||||
|
let limit = body
|
||||||
|
.limit
|
||||||
|
.and_then(|u| u32::try_from(u).ok())
|
||||||
|
.map_or(10_usize, |u| u as usize)
|
||||||
|
.min(100);
|
||||||
|
|
||||||
|
let res = services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.paginate_relations_with_filter(
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
&body.event_id,
|
||||||
|
Some(body.event_type.clone()),
|
||||||
|
Some(body.rel_type.clone()),
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
limit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
get_relating_events_with_rel_type_and_event_type::v1::Response {
|
||||||
|
chunk: res.chunk,
|
||||||
|
next_batch: res.next_batch,
|
||||||
|
prev_batch: res.prev_batch,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}`
|
||||||
|
pub async fn get_relating_events_with_rel_type_route(
|
||||||
|
body: Ruma<get_relating_events_with_rel_type::v1::Request>,
|
||||||
|
) -> Result<get_relating_events_with_rel_type::v1::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
let from = match body.from.clone() {
|
||||||
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
|
None => match ruma::api::Direction::Backward {
|
||||||
|
// TODO: fix ruma so `body.dir` exists
|
||||||
|
ruma::api::Direction::Forward => PduCount::min(),
|
||||||
|
ruma::api::Direction::Backward => PduCount::max(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let to = body
|
||||||
|
.to
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
|
// Use limit or else 10, with maximum 100
|
||||||
|
let limit = body
|
||||||
|
.limit
|
||||||
|
.and_then(|u| u32::try_from(u).ok())
|
||||||
|
.map_or(10_usize, |u| u as usize)
|
||||||
|
.min(100);
|
||||||
|
|
||||||
|
let res = services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.paginate_relations_with_filter(
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
&body.event_id,
|
||||||
|
None,
|
||||||
|
Some(body.rel_type.clone()),
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
limit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(get_relating_events_with_rel_type::v1::Response {
|
||||||
|
chunk: res.chunk,
|
||||||
|
next_batch: res.next_batch,
|
||||||
|
prev_batch: res.prev_batch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}`
|
||||||
|
pub async fn get_relating_events_route(
|
||||||
|
body: Ruma<get_relating_events::v1::Request>,
|
||||||
|
) -> Result<get_relating_events::v1::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
let from = match body.from.clone() {
|
||||||
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
|
None => match ruma::api::Direction::Backward {
|
||||||
|
// TODO: fix ruma so `body.dir` exists
|
||||||
|
ruma::api::Direction::Forward => PduCount::min(),
|
||||||
|
ruma::api::Direction::Backward => PduCount::max(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let to = body
|
||||||
|
.to
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
|
// Use limit or else 10, with maximum 100
|
||||||
|
let limit = body
|
||||||
|
.limit
|
||||||
|
.and_then(|u| u32::try_from(u).ok())
|
||||||
|
.map_or(10_usize, |u| u as usize)
|
||||||
|
.min(100);
|
||||||
|
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.paginate_relations_with_filter(
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
&body.event_id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ use ruma::{
|
||||||
tombstone::RoomTombstoneEventContent,
|
tombstone::RoomTombstoneEventContent,
|
||||||
topic::RoomTopicEventContent,
|
topic::RoomTopicEventContent,
|
||||||
},
|
},
|
||||||
RoomEventType, StateEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
int,
|
int,
|
||||||
serde::JsonObject,
|
serde::JsonObject,
|
||||||
|
@ -142,8 +142,9 @@ pub async fn create_room_route(
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// TODO: Add correct value for v11
|
||||||
let mut content = serde_json::from_str::<CanonicalJsonObject>(
|
let mut content = serde_json::from_str::<CanonicalJsonObject>(
|
||||||
to_raw_value(&RoomCreateEventContent::new(sender_user.clone()))
|
to_raw_value(&RoomCreateEventContent::new_v1(sender_user.clone()))
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
||||||
.get(),
|
.get(),
|
||||||
)
|
)
|
||||||
|
@ -175,7 +176,7 @@ pub async fn create_room_route(
|
||||||
// 1. The room create event
|
// 1. The room create event
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomCreate,
|
event_type: TimelineEventType::RoomCreate,
|
||||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some("".to_owned()),
|
state_key: Some("".to_owned()),
|
||||||
|
@ -189,7 +190,7 @@ pub async fn create_room_route(
|
||||||
// 2. Let the room creator join
|
// 2. Let the room creator join
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Join,
|
membership: MembershipState::Join,
|
||||||
displayname: services().users.displayname(sender_user)?,
|
displayname: services().users.displayname(sender_user)?,
|
||||||
|
@ -247,7 +248,7 @@ pub async fn create_room_route(
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&power_levels_content)
|
content: to_raw_value(&power_levels_content)
|
||||||
.expect("to_raw_value always works on serde_json::Value"),
|
.expect("to_raw_value always works on serde_json::Value"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -263,7 +264,7 @@ pub async fn create_room_route(
|
||||||
if let Some(room_alias_id) = &alias {
|
if let Some(room_alias_id) = &alias {
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomCanonicalAlias,
|
event_type: TimelineEventType::RoomCanonicalAlias,
|
||||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||||
alias: Some(room_alias_id.to_owned()),
|
alias: Some(room_alias_id.to_owned()),
|
||||||
alt_aliases: vec![],
|
alt_aliases: vec![],
|
||||||
|
@ -284,7 +285,7 @@ pub async fn create_room_route(
|
||||||
// 5.1 Join Rules
|
// 5.1 Join Rules
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomJoinRules,
|
event_type: TimelineEventType::RoomJoinRules,
|
||||||
content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
|
content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
|
||||||
RoomPreset::PublicChat => JoinRule::Public,
|
RoomPreset::PublicChat => JoinRule::Public,
|
||||||
// according to spec "invite" is the default
|
// according to spec "invite" is the default
|
||||||
|
@ -303,7 +304,7 @@ pub async fn create_room_route(
|
||||||
// 5.2 History Visibility
|
// 5.2 History Visibility
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomHistoryVisibility,
|
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||||
HistoryVisibility::Shared,
|
HistoryVisibility::Shared,
|
||||||
))
|
))
|
||||||
|
@ -320,7 +321,7 @@ pub async fn create_room_route(
|
||||||
// 5.3 Guest Access
|
// 5.3 Guest Access
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomGuestAccess,
|
event_type: TimelineEventType::RoomGuestAccess,
|
||||||
content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
|
content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
|
||||||
RoomPreset::PublicChat => GuestAccess::Forbidden,
|
RoomPreset::PublicChat => GuestAccess::Forbidden,
|
||||||
_ => GuestAccess::CanJoin,
|
_ => GuestAccess::CanJoin,
|
||||||
|
@ -346,7 +347,7 @@ pub async fn create_room_route(
|
||||||
pdu_builder.state_key.get_or_insert_with(|| "".to_owned());
|
pdu_builder.state_key.get_or_insert_with(|| "".to_owned());
|
||||||
|
|
||||||
// Silently skip encryption events if they are not allowed
|
// Silently skip encryption events if they are not allowed
|
||||||
if pdu_builder.event_type == RoomEventType::RoomEncryption
|
if pdu_builder.event_type == TimelineEventType::RoomEncryption
|
||||||
&& !services().globals.allow_encryption()
|
&& !services().globals.allow_encryption()
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
@ -364,8 +365,8 @@ pub async fn create_room_route(
|
||||||
if let Some(name) = &body.name {
|
if let Some(name) = &body.name {
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomName,
|
event_type: TimelineEventType::RoomName,
|
||||||
content: to_raw_value(&RoomNameEventContent::new(Some(name.clone())))
|
content: to_raw_value(&RoomNameEventContent::new(name.clone()))
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some("".to_owned()),
|
state_key: Some("".to_owned()),
|
||||||
|
@ -380,7 +381,7 @@ pub async fn create_room_route(
|
||||||
if let Some(topic) = &body.topic {
|
if let Some(topic) = &body.topic {
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomTopic,
|
event_type: TimelineEventType::RoomTopic,
|
||||||
content: to_raw_value(&RoomTopicEventContent {
|
content: to_raw_value(&RoomTopicEventContent {
|
||||||
topic: topic.clone(),
|
topic: topic.clone(),
|
||||||
})
|
})
|
||||||
|
@ -429,7 +430,10 @@ pub async fn get_room_event_route(
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu(&body.event_id)?
|
.get_pdu(&body.event_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
.ok_or_else(|| {
|
||||||
|
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||||
|
})?;
|
||||||
|
|
||||||
if !services().rooms.state_accessor.user_can_see_event(
|
if !services().rooms.state_accessor.user_can_see_event(
|
||||||
sender_user,
|
sender_user,
|
||||||
|
@ -442,6 +446,9 @@ pub async fn get_room_event_route(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut event = (*event).clone();
|
||||||
|
event.add_age()?;
|
||||||
|
|
||||||
Ok(get_room_event::v3::Response {
|
Ok(get_room_event::v3::Response {
|
||||||
event: event.to_room_event(),
|
event: event.to_room_event(),
|
||||||
})
|
})
|
||||||
|
@ -526,7 +533,7 @@ pub async fn upgrade_room_route(
|
||||||
// Fail if the sender does not have the required permissions
|
// Fail if the sender does not have the required permissions
|
||||||
let tombstone_event_id = services().rooms.timeline.build_and_append_pdu(
|
let tombstone_event_id = services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomTombstone,
|
event_type: TimelineEventType::RoomTombstone,
|
||||||
content: to_raw_value(&RoomTombstoneEventContent {
|
content: to_raw_value(&RoomTombstoneEventContent {
|
||||||
body: "This room has been replaced".to_owned(),
|
body: "This room has been replaced".to_owned(),
|
||||||
replacement_room: replacement_room.clone(),
|
replacement_room: replacement_room.clone(),
|
||||||
|
@ -608,7 +615,7 @@ pub async fn upgrade_room_route(
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomCreate,
|
event_type: TimelineEventType::RoomCreate,
|
||||||
content: to_raw_value(&create_event_content)
|
content: to_raw_value(&create_event_content)
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -623,7 +630,7 @@ pub async fn upgrade_room_route(
|
||||||
// Join the new room
|
// Join the new room
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Join,
|
membership: MembershipState::Join,
|
||||||
displayname: services().users.displayname(sender_user)?,
|
displayname: services().users.displayname(sender_user)?,
|
||||||
|
@ -716,7 +723,7 @@ pub async fn upgrade_room_route(
|
||||||
// Modify the power levels in the old room to prevent sending of events and inviting new users
|
// Modify the power levels in the old room to prevent sending of events and inviting new users
|
||||||
let _ = services().rooms.timeline.build_and_append_pdu(
|
let _ = services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&power_levels_event_content)
|
content: to_raw_value(&power_levels_event_content)
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
|
|
@ -31,7 +31,8 @@ pub async fn search_events_route(
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
|
// Use limit or else 10, with maximum 100
|
||||||
|
let limit = filter.limit.map_or(10, u64::from).min(100) as usize;
|
||||||
|
|
||||||
let mut searches = Vec::new();
|
let mut searches = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ruma::{
|
||||||
UserId,
|
UserId,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::info;
|
use tracing::{info, warn};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
|
@ -26,6 +26,7 @@ pub async fn get_login_types_route(
|
||||||
) -> Result<get_login_types::v3::Response> {
|
) -> Result<get_login_types::v3::Response> {
|
||||||
Ok(get_login_types::v3::Response::new(vec![
|
Ok(get_login_types::v3::Response::new(vec![
|
||||||
get_login_types::v3::LoginType::Password(Default::default()),
|
get_login_types::v3::LoginType::Password(Default::default()),
|
||||||
|
get_login_types::v3::LoginType::ApplicationService(Default::default()),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,23 +42,31 @@ pub async fn get_login_types_route(
|
||||||
/// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
|
/// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
|
||||||
/// supported login types.
|
/// supported login types.
|
||||||
pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Response> {
|
pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Response> {
|
||||||
|
// To allow deprecated login methods
|
||||||
|
#![allow(deprecated)]
|
||||||
// Validate login method
|
// Validate login method
|
||||||
// TODO: Other login methods
|
// TODO: Other login methods
|
||||||
let user_id = match &body.login_info {
|
let user_id = match &body.login_info {
|
||||||
login::v3::LoginInfo::Password(login::v3::Password {
|
login::v3::LoginInfo::Password(login::v3::Password {
|
||||||
identifier,
|
identifier,
|
||||||
password,
|
password,
|
||||||
|
user,
|
||||||
|
address: _,
|
||||||
|
medium: _,
|
||||||
}) => {
|
}) => {
|
||||||
let username = if let UserIdentifier::UserIdOrLocalpart(user_id) = identifier {
|
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||||
user_id.to_lowercase()
|
UserId::parse_with_server_name(
|
||||||
|
user_id.to_lowercase(),
|
||||||
|
services().globals.server_name(),
|
||||||
|
)
|
||||||
|
} else if let Some(user) = user {
|
||||||
|
UserId::parse(user)
|
||||||
} else {
|
} else {
|
||||||
|
warn!("Bad login type: {:?}", &body.login_info);
|
||||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||||
};
|
}
|
||||||
let user_id =
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||||
UserId::parse_with_server_name(username, services().globals.server_name())
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
|
|
||||||
})?;
|
|
||||||
let hash = services()
|
let hash = services()
|
||||||
.users
|
.users
|
||||||
.password_hash(&user_id)?
|
.password_hash(&user_id)?
|
||||||
|
@ -103,7 +112,31 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
|
||||||
|
identifier,
|
||||||
|
user,
|
||||||
|
}) => {
|
||||||
|
if !body.from_appservice {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::Forbidden,
|
||||||
|
"Forbidden login type.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||||
|
UserId::parse_with_server_name(
|
||||||
|
user_id.to_lowercase(),
|
||||||
|
services().globals.server_name(),
|
||||||
|
)
|
||||||
|
} else if let Some(user) = user {
|
||||||
|
UserId::parse(user)
|
||||||
|
} else {
|
||||||
|
warn!("Bad login type: {:?}", &body.login_info);
|
||||||
|
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||||
|
}
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
warn!("Unsupported or unknown login type: {:?}", &body.login_info);
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Unknown,
|
ErrorKind::Unknown,
|
||||||
"Unsupported login type.",
|
"Unsupported login type.",
|
||||||
|
@ -141,6 +174,8 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
|
||||||
|
|
||||||
info!("{} logged in", user_id);
|
info!("{} logged in", user_id);
|
||||||
|
|
||||||
|
// Homeservers are still required to send the `home_server` field
|
||||||
|
#[allow(deprecated)]
|
||||||
Ok(login::v3::Response {
|
Ok(login::v3::Response {
|
||||||
user_id,
|
user_id,
|
||||||
access_token: token,
|
access_token: token,
|
||||||
|
|
34
src/api/client_server/space.rs
Normal file
34
src/api/client_server/space.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::{services, Result, Ruma};
|
||||||
|
use ruma::api::client::space::get_hierarchy;
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
|
||||||
|
///
|
||||||
|
/// Paginates over the space tree in a depth-first manner to locate child rooms of a given space.
|
||||||
|
pub async fn get_hierarchy_route(
|
||||||
|
body: Ruma<get_hierarchy::v1::Request>,
|
||||||
|
) -> Result<get_hierarchy::v1::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
let skip = body
|
||||||
|
.from
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| s.parse::<usize>().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let limit = body.limit.map_or(10, u64::from).min(100) as usize;
|
||||||
|
|
||||||
|
let max_depth = body.max_depth.map_or(3, u64::from).min(10) as usize + 1; // +1 to skip the space room itself
|
||||||
|
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.spaces
|
||||||
|
.get_hierarchy(
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
limit,
|
||||||
|
skip,
|
||||||
|
max_depth,
|
||||||
|
body.suggested_only,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ use ruma::{
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, RoomId, UserId,
|
EventId, RoomId, UserId,
|
||||||
};
|
};
|
||||||
|
use tracing::log::warn;
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`
|
/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`
|
||||||
///
|
///
|
||||||
|
@ -84,7 +85,7 @@ pub async fn get_state_events_route(
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -117,7 +118,7 @@ pub async fn get_state_events_for_key_route(
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -129,10 +130,13 @@ pub async fn get_state_events_for_key_route(
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
|
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or_else(|| {
|
||||||
ErrorKind::NotFound,
|
warn!(
|
||||||
"State event not found.",
|
"State event {:?} not found in room {:?}",
|
||||||
))?;
|
&body.event_type, &body.room_id
|
||||||
|
);
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(get_state_events_for_key::v3::Response {
|
Ok(get_state_events_for_key::v3::Response {
|
||||||
content: serde_json::from_str(event.content.get())
|
content: serde_json::from_str(event.content.get())
|
||||||
|
@ -153,7 +157,7 @@ pub async fn get_state_events_for_empty_key_route(
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -165,10 +169,13 @@ pub async fn get_state_events_for_empty_key_route(
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&body.room_id, &body.event_type, "")?
|
.room_state_get(&body.room_id, &body.event_type, "")?
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or_else(|| {
|
||||||
ErrorKind::NotFound,
|
warn!(
|
||||||
"State event not found.",
|
"State event {:?} not found in room {:?}",
|
||||||
))?;
|
&body.event_type, &body.room_id
|
||||||
|
);
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(get_state_events_for_key::v3::Response {
|
Ok(get_state_events_for_key::v3::Response {
|
||||||
content: serde_json::from_str(event.content.get())
|
content: serde_json::from_str(event.content.get())
|
||||||
|
|
File diff suppressed because it is too large
Load diff
49
src/api/client_server/threads.rs
Normal file
49
src/api/client_server/threads.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use ruma::api::client::{error::ErrorKind, threads::get_threads};
|
||||||
|
|
||||||
|
use crate::{services, Error, Result, Ruma};
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/threads`
|
||||||
|
pub async fn get_threads_route(
|
||||||
|
body: Ruma<get_threads::v1::Request>,
|
||||||
|
) -> Result<get_threads::v1::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
// Use limit or else 10, with maximum 100
|
||||||
|
let limit = body
|
||||||
|
.limit
|
||||||
|
.and_then(|l| l.try_into().ok())
|
||||||
|
.unwrap_or(10)
|
||||||
|
.min(100);
|
||||||
|
|
||||||
|
let from = if let Some(from) = &body.from {
|
||||||
|
from.parse()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, ""))?
|
||||||
|
} else {
|
||||||
|
u64::MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
let threads = services()
|
||||||
|
.rooms
|
||||||
|
.threads
|
||||||
|
.threads_until(sender_user, &body.room_id, from, &body.include)?
|
||||||
|
.take(limit)
|
||||||
|
.filter_map(|r| r.ok())
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let next_batch = threads.last().map(|(count, _)| count.to_string());
|
||||||
|
|
||||||
|
Ok(get_threads::v1::Response {
|
||||||
|
chunk: threads
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, pdu)| pdu.to_room_event())
|
||||||
|
.collect(),
|
||||||
|
next_batch,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
use ruma::events::ToDeviceEventType;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{services, Error, Result, Ruma};
|
||||||
|
@ -42,7 +41,7 @@ pub async fn send_event_to_device_route(
|
||||||
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(
|
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(
|
||||||
DirectDeviceContent {
|
DirectDeviceContent {
|
||||||
sender: sender_user.clone(),
|
sender: sender_user.clone(),
|
||||||
ev_type: ToDeviceEventType::from(&*body.event_type),
|
ev_type: body.event_type.clone(),
|
||||||
message_id: count.to_string().into(),
|
message_id: count.to_string().into(),
|
||||||
messages,
|
messages,
|
||||||
},
|
},
|
||||||
|
@ -60,7 +59,7 @@ pub async fn send_event_to_device_route(
|
||||||
sender_user,
|
sender_user,
|
||||||
target_user_id,
|
target_user_id,
|
||||||
target_device_id,
|
target_device_id,
|
||||||
&body.event_type,
|
&body.event_type.to_string(),
|
||||||
event.deserialize_as().map_err(|_| {
|
event.deserialize_as().map_err(|_| {
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||||
})?,
|
})?,
|
||||||
|
@ -73,7 +72,7 @@ pub async fn send_event_to_device_route(
|
||||||
sender_user,
|
sender_user,
|
||||||
target_user_id,
|
target_user_id,
|
||||||
&target_device_id?,
|
&target_device_id?,
|
||||||
&body.event_type,
|
&body.event_type.to_string(),
|
||||||
event.deserialize_as().map_err(|_| {
|
event.deserialize_as().map_err(|_| {
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||||
})?,
|
})?,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{collections::BTreeMap, iter::FromIterator};
|
use std::{collections::BTreeMap, iter::FromIterator};
|
||||||
|
|
||||||
use ruma::api::client::discovery::get_supported_versions;
|
use axum::{response::IntoResponse, Json};
|
||||||
|
use ruma::api::client::{discovery::get_supported_versions, error::ErrorKind};
|
||||||
|
|
||||||
use crate::{Result, Ruma};
|
use crate::{services, Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/versions`
|
/// # `GET /_matrix/client/versions`
|
||||||
///
|
///
|
||||||
|
@ -23,9 +24,27 @@ pub async fn get_supported_versions_route(
|
||||||
"r0.6.0".to_owned(),
|
"r0.6.0".to_owned(),
|
||||||
"v1.1".to_owned(),
|
"v1.1".to_owned(),
|
||||||
"v1.2".to_owned(),
|
"v1.2".to_owned(),
|
||||||
|
"v1.3".to_owned(),
|
||||||
|
"v1.4".to_owned(),
|
||||||
|
"v1.5".to_owned(),
|
||||||
],
|
],
|
||||||
unstable_features: BTreeMap::from_iter([("org.matrix.e2e_cross_signing".to_owned(), true)]),
|
unstable_features: BTreeMap::from_iter([("org.matrix.e2e_cross_signing".to_owned(), true)]),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `GET /.well-known/matrix/client`
|
||||||
|
pub async fn well_known_client_route(
|
||||||
|
_body: Ruma<get_supported_versions::Request>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
let client_url = match services().globals.well_known_client() {
|
||||||
|
Some(url) => url.clone(),
|
||||||
|
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"m.homeserver": {"base_url": client_url},
|
||||||
|
"org.matrix.msc3575.proxy": {"url": client_url}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{services, Result, Ruma};
|
use crate::{services, Result, Ruma};
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch};
|
use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch};
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
|
@ -28,7 +29,7 @@ pub async fn turn_server_route(
|
||||||
.expect("HMAC can take key of any size");
|
.expect("HMAC can take key of any size");
|
||||||
mac.update(username.as_bytes());
|
mac.update(username.as_bytes());
|
||||||
|
|
||||||
let password: String = base64::encode_config(mac.finalize().into_bytes(), base64::STANDARD);
|
let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
|
||||||
|
|
||||||
(username, password)
|
(username, password)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,18 +3,16 @@ use std::{collections::BTreeMap, iter::FromIterator, str};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
body::{Full, HttpBody},
|
body::{Full, HttpBody},
|
||||||
extract::{
|
extract::{rejection::TypedHeaderRejectionReason, FromRequest, Path, TypedHeader},
|
||||||
rejection::TypedHeaderRejectionReason, FromRequest, Path, RequestParts, TypedHeader,
|
|
||||||
},
|
|
||||||
headers::{
|
headers::{
|
||||||
authorization::{Bearer, Credentials},
|
authorization::{Bearer, Credentials},
|
||||||
Authorization,
|
Authorization,
|
||||||
},
|
},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
BoxError,
|
BoxError, RequestExt, RequestPartsExt,
|
||||||
};
|
};
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
use http::StatusCode;
|
use http::{Request, StatusCode};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{client::error::ErrorKind, AuthScheme, IncomingRequest, OutgoingResponse},
|
api::{client::error::ErrorKind, AuthScheme, IncomingRequest, OutgoingResponse},
|
||||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, UserId,
|
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, UserId,
|
||||||
|
@ -26,28 +24,45 @@ use super::{Ruma, RumaResponse};
|
||||||
use crate::{services, Error, Result};
|
use crate::{services, Error, Result};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T, B> FromRequest<B> for Ruma<T>
|
impl<T, S, B> FromRequest<S, B> for Ruma<T>
|
||||||
where
|
where
|
||||||
T: IncomingRequest,
|
T: IncomingRequest,
|
||||||
B: HttpBody + Send,
|
B: HttpBody + Send + 'static,
|
||||||
B::Data: Send,
|
B::Data: Send,
|
||||||
B::Error: Into<BoxError>,
|
B::Error: Into<BoxError>,
|
||||||
{
|
{
|
||||||
type Rejection = Error;
|
type Rejection = Error;
|
||||||
|
|
||||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct QueryParams {
|
struct QueryParams {
|
||||||
access_token: Option<String>,
|
access_token: Option<String>,
|
||||||
user_id: Option<String>,
|
user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata = T::METADATA;
|
let (mut parts, mut body) = match req.with_limited_body() {
|
||||||
let auth_header = Option::<TypedHeader<Authorization<Bearer>>>::from_request(req).await?;
|
Ok(limited_req) => {
|
||||||
let path_params = Path::<Vec<String>>::from_request(req).await?;
|
let (parts, body) = limited_req.into_parts();
|
||||||
|
let body = to_bytes(body)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
||||||
|
(parts, body)
|
||||||
|
}
|
||||||
|
Err(original_req) => {
|
||||||
|
let (parts, body) = original_req.into_parts();
|
||||||
|
let body = to_bytes(body)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
||||||
|
(parts, body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let query = req.uri().query().unwrap_or_default();
|
let metadata = T::METADATA;
|
||||||
let query_params: QueryParams = match ruma::serde::urlencoded::from_str(query) {
|
let auth_header: Option<TypedHeader<Authorization<Bearer>>> = parts.extract().await?;
|
||||||
|
let path_params: Path<Vec<String>> = parts.extract().await?;
|
||||||
|
|
||||||
|
let query = parts.uri.query().unwrap_or_default();
|
||||||
|
let query_params: QueryParams = match serde_html_form::from_str(query) {
|
||||||
Ok(params) => params,
|
Ok(params) => params,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(%query, "Failed to deserialize query parameters: {}", e);
|
error!(%query, "Failed to deserialize query parameters: {}", e);
|
||||||
|
@ -63,10 +78,6 @@ where
|
||||||
None => query_params.access_token.as_deref(),
|
None => query_params.access_token.as_deref(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut body = Bytes::from_request(req)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
|
||||||
|
|
||||||
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
|
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
|
||||||
|
|
||||||
let appservices = services().appservice.all().unwrap();
|
let appservices = services().appservice.all().unwrap();
|
||||||
|
@ -138,8 +149,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthScheme::ServerSignatures => {
|
AuthScheme::ServerSignatures => {
|
||||||
let TypedHeader(Authorization(x_matrix)) =
|
let TypedHeader(Authorization(x_matrix)) = parts
|
||||||
TypedHeader::<Authorization<XMatrix>>::from_request(req)
|
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!("Missing or invalid Authorization header: {}", e);
|
warn!("Missing or invalid Authorization header: {}", e);
|
||||||
|
@ -170,11 +181,11 @@ where
|
||||||
let mut request_map = BTreeMap::from_iter([
|
let mut request_map = BTreeMap::from_iter([
|
||||||
(
|
(
|
||||||
"method".to_owned(),
|
"method".to_owned(),
|
||||||
CanonicalJsonValue::String(req.method().to_string()),
|
CanonicalJsonValue::String(parts.method.to_string()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"uri".to_owned(),
|
"uri".to_owned(),
|
||||||
CanonicalJsonValue::String(req.uri().to_string()),
|
CanonicalJsonValue::String(parts.uri.to_string()),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"origin".to_owned(),
|
"origin".to_owned(),
|
||||||
|
@ -224,7 +235,7 @@ where
|
||||||
x_matrix.origin, e, request_map
|
x_matrix.origin, e, request_map
|
||||||
);
|
);
|
||||||
|
|
||||||
if req.uri().to_string().contains('@') {
|
if parts.uri.to_string().contains('@') {
|
||||||
warn!(
|
warn!(
|
||||||
"Request uri contained '@' character. Make sure your \
|
"Request uri contained '@' character. Make sure your \
|
||||||
reverse proxy gives Conduit the raw uri (apache: use \
|
reverse proxy gives Conduit the raw uri (apache: use \
|
||||||
|
@ -243,8 +254,8 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut http_request = http::Request::builder().uri(req.uri()).method(req.method());
|
let mut http_request = http::Request::builder().uri(parts.uri).method(parts.method);
|
||||||
*http_request.headers_mut().unwrap() = req.headers().clone();
|
*http_request.headers_mut().unwrap() = parts.headers;
|
||||||
|
|
||||||
if let Some(CanonicalJsonValue::Object(json_body)) = &mut json_body {
|
if let Some(CanonicalJsonValue::Object(json_body)) = &mut json_body {
|
||||||
let user_id = sender_user.clone().unwrap_or_else(|| {
|
let user_id = sender_user.clone().unwrap_or_else(|| {
|
||||||
|
@ -281,10 +292,8 @@ where
|
||||||
debug!("{:?}", http_request);
|
debug!("{:?}", http_request);
|
||||||
|
|
||||||
let body = T::try_from_http_request(http_request, &path_params).map_err(|e| {
|
let body = T::try_from_http_request(http_request, &path_params).map_err(|e| {
|
||||||
warn!(
|
warn!("try_from_http_request failed: {:?}", e);
|
||||||
"try_from_http_request failed: {:?}\nJSON body: {:?}",
|
debug!("JSON body: {:?}", json_body);
|
||||||
e, json_body
|
|
||||||
);
|
|
||||||
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
|
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -364,3 +373,55 @@ impl<T: OutgoingResponse> IntoResponse for RumaResponse<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copied from hyper under the following license:
|
||||||
|
// Copyright (c) 2014-2021 Sean McArthur
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
pub(crate) async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
|
||||||
|
where
|
||||||
|
T: HttpBody,
|
||||||
|
{
|
||||||
|
futures_util::pin_mut!(body);
|
||||||
|
|
||||||
|
// If there's only 1 chunk, we can just return Buf::to_bytes()
|
||||||
|
let mut first = if let Some(buf) = body.data().await {
|
||||||
|
buf?
|
||||||
|
} else {
|
||||||
|
return Ok(Bytes::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
let second = if let Some(buf) = body.data().await {
|
||||||
|
buf?
|
||||||
|
} else {
|
||||||
|
return Ok(first.copy_to_bytes(first.remaining()));
|
||||||
|
};
|
||||||
|
|
||||||
|
// With more than 1 buf, we gotta flatten into a Vec first.
|
||||||
|
let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;
|
||||||
|
let mut vec = Vec::with_capacity(cap);
|
||||||
|
vec.put(first);
|
||||||
|
vec.put(second);
|
||||||
|
|
||||||
|
while let Some(buf) = body.data().await {
|
||||||
|
vec.put(buf?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec.into())
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||||
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
||||||
|
@ -18,11 +20,7 @@ use ruma::{
|
||||||
discovery::{get_server_keys, get_server_version, ServerSigningKeys, VerifyKey},
|
discovery::{get_server_keys, get_server_version, ServerSigningKeys, VerifyKey},
|
||||||
event::{get_event, get_missing_events, get_room_state, get_room_state_ids},
|
event::{get_event, get_missing_events, get_room_state, get_room_state_ids},
|
||||||
keys::{claim_keys, get_keys},
|
keys::{claim_keys, get_keys},
|
||||||
membership::{
|
membership::{create_invite, create_join_event, prepare_join_event},
|
||||||
create_invite,
|
|
||||||
create_join_event::{self, RoomState},
|
|
||||||
prepare_join_event,
|
|
||||||
},
|
|
||||||
query::{get_profile_information, get_room_information},
|
query::{get_profile_information, get_room_information},
|
||||||
transactions::{
|
transactions::{
|
||||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||||
|
@ -39,7 +37,7 @@ use ruma::{
|
||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
},
|
},
|
||||||
RoomEventType, StateEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
serde::{Base64, JsonObject, Raw},
|
serde::{Base64, JsonObject, Raw},
|
||||||
to_device::DeviceIdOrAllDevices,
|
to_device::DeviceIdOrAllDevices,
|
||||||
|
@ -57,7 +55,7 @@ use std::{
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
||||||
/// (colon-plus-port if it was specified).
|
/// (colon-plus-port if it was specified).
|
||||||
|
@ -125,6 +123,12 @@ where
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if destination == services().globals.server_name() {
|
||||||
|
return Err(Error::bad_config(
|
||||||
|
"Won't send federation request to ourselves",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
debug!("Preparing to send request to {destination}");
|
debug!("Preparing to send request to {destination}");
|
||||||
|
|
||||||
let mut write_destination_to_cache = false;
|
let mut write_destination_to_cache = false;
|
||||||
|
@ -153,7 +157,7 @@ where
|
||||||
.try_into_http_request::<Vec<u8>>(
|
.try_into_http_request::<Vec<u8>>(
|
||||||
&actual_destination_str,
|
&actual_destination_str,
|
||||||
SendAccessToken::IfRequired(""),
|
SendAccessToken::IfRequired(""),
|
||||||
&[MatrixVersion::V1_0],
|
&[MatrixVersion::V1_4],
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!(
|
warn!(
|
||||||
|
@ -337,7 +341,7 @@ fn add_port_to_hostname(destination_str: &str) -> FedDest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns: actual_destination, host header
|
/// Returns: actual_destination, host header
|
||||||
/// Implemented according to the specification at https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names
|
/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
|
||||||
/// Numbers in comments below refer to bullet points in linked section of specification
|
/// Numbers in comments below refer to bullet points in linked section of specification
|
||||||
async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDest) {
|
async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDest) {
|
||||||
debug!("Finding actual destination for {destination}");
|
debug!("Finding actual destination for {destination}");
|
||||||
|
@ -501,6 +505,10 @@ async fn request_well_known(destination: &str) -> Option<String> {
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
debug!("Got well known response");
|
debug!("Got well known response");
|
||||||
|
if let Err(e) = &response {
|
||||||
|
debug!("Well known error: {e:?}");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let text = response.ok()?.text().await;
|
let text = response.ok()?.text().await;
|
||||||
debug!("Got well known response text");
|
debug!("Got well known response text");
|
||||||
let body: serde_json::Value = serde_json::from_str(&text.ok()?).ok()?;
|
let body: serde_json::Value = serde_json::from_str(&text.ok()?).ok()?;
|
||||||
|
@ -658,7 +666,7 @@ pub fn parse_incoming_pdu(
|
||||||
|
|
||||||
let room_version_id = services().rooms.state.get_room_version(&room_id)?;
|
let room_version_id = services().rooms.state.get_room_version(&room_id)?;
|
||||||
|
|
||||||
let (event_id, value) = match gen_event_id_canonical_json(&pdu, &room_version_id) {
|
let (event_id, value) = match gen_event_id_canonical_json(pdu, &room_version_id) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Event could not be converted to canonical json
|
// Event could not be converted to canonical json
|
||||||
|
@ -699,21 +707,34 @@ pub async fn send_transaction_message_route(
|
||||||
// let mut auth_cache = EventMap::new();
|
// let mut auth_cache = EventMap::new();
|
||||||
|
|
||||||
for pdu in &body.pdus {
|
for pdu in &body.pdus {
|
||||||
let r = parse_incoming_pdu(&pdu);
|
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
|
||||||
|
warn!("Error parsing incoming event {:?}: {:?}", pdu, e);
|
||||||
|
Error::BadServerResponse("Invalid PDU in server response")
|
||||||
|
})?;
|
||||||
|
let room_id: OwnedRoomId = value
|
||||||
|
.get("room_id")
|
||||||
|
.and_then(|id| RoomId::parse(id.as_str()?).ok())
|
||||||
|
.ok_or(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Invalid room id in pdu",
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if services().rooms.state.get_room_version(&room_id).is_err() {
|
||||||
|
debug!("Server is not in room {room_id}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = parse_incoming_pdu(pdu);
|
||||||
let (event_id, value, room_id) = match r {
|
let (event_id, value, room_id) = match r {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Could not parse pdu: {e}");
|
warn!("Could not parse PDU: {e}");
|
||||||
|
warn!("Full PDU: {:?}", &pdu);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||||
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.event_handler
|
|
||||||
.acl_check(sender_servername, &room_id)?;
|
|
||||||
|
|
||||||
let mutex = Arc::clone(
|
let mutex = Arc::clone(
|
||||||
services()
|
services()
|
||||||
.globals
|
.globals
|
||||||
|
@ -804,7 +825,7 @@ pub async fn send_transaction_message_route(
|
||||||
.readreceipt_update(&user_id, &room_id, event)?;
|
.readreceipt_update(&user_id, &room_id, event)?;
|
||||||
} else {
|
} else {
|
||||||
// TODO fetch missing events
|
// TODO fetch missing events
|
||||||
info!("No known event ids in read receipt: {:?}", user_updates);
|
debug!("No known event ids in read receipt: {:?}", user_updates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -908,6 +929,7 @@ pub async fn send_transaction_message_route(
|
||||||
&master_key,
|
&master_key,
|
||||||
&self_signing_key,
|
&self_signing_key,
|
||||||
&None,
|
&None,
|
||||||
|
true,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -918,7 +940,7 @@ pub async fn send_transaction_message_route(
|
||||||
Ok(send_transaction_message::v1::Response {
|
Ok(send_transaction_message::v1::Response {
|
||||||
pdus: resolved_map
|
pdus: resolved_map
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(e, r)| (e, r.map_err(|e| e.to_string())))
|
.map(|(e, r)| (e, r.map_err(|e| e.sanitized_error())))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -944,7 +966,10 @@ pub async fn get_event_route(
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu_json(&body.event_id)?
|
.get_pdu_json(&body.event_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
.ok_or_else(|| {
|
||||||
|
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||||
|
})?;
|
||||||
|
|
||||||
let room_id_str = event
|
let room_id_str = event
|
||||||
.get("room_id")
|
.get("room_id")
|
||||||
|
@ -967,7 +992,7 @@ pub async fn get_event_route(
|
||||||
|
|
||||||
if !services().rooms.state_accessor.server_can_see_event(
|
if !services().rooms.state_accessor.server_can_see_event(
|
||||||
sender_servername,
|
sender_servername,
|
||||||
&room_id,
|
room_id,
|
||||||
&body.event_id,
|
&body.event_id,
|
||||||
)? {
|
)? {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
|
@ -999,7 +1024,7 @@ pub async fn get_backfill_route(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("server is authenticated");
|
.expect("server is authenticated");
|
||||||
|
|
||||||
info!("Got backfill request from: {}", sender_servername);
|
debug!("Got backfill request from: {}", sender_servername);
|
||||||
|
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -1033,7 +1058,7 @@ pub async fn get_backfill_route(
|
||||||
let all_events = services()
|
let all_events = services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_until(&user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
|
.pdus_until(user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
|
||||||
.take(limit.try_into().unwrap());
|
.take(limit.try_into().unwrap());
|
||||||
|
|
||||||
let events = all_events
|
let events = all_events
|
||||||
|
@ -1050,7 +1075,7 @@ pub async fn get_backfill_route(
|
||||||
})
|
})
|
||||||
.map(|(_, pdu)| services().rooms.timeline.get_pdu_json(&pdu.event_id))
|
.map(|(_, pdu)| services().rooms.timeline.get_pdu_json(&pdu.event_id))
|
||||||
.filter_map(|r| r.ok().flatten())
|
.filter_map(|r| r.ok().flatten())
|
||||||
.map(|pdu| PduEvent::convert_to_outgoing_federation_event(pdu))
|
.map(PduEvent::convert_to_outgoing_federation_event)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(get_backfill::v1::Response {
|
Ok(get_backfill::v1::Response {
|
||||||
|
@ -1184,7 +1209,10 @@ pub async fn get_event_authorization_route(
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu_json(&body.event_id)?
|
.get_pdu_json(&body.event_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
.ok_or_else(|| {
|
||||||
|
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||||
|
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||||
|
})?;
|
||||||
|
|
||||||
let room_id_str = event
|
let room_id_str = event
|
||||||
.get("room_id")
|
.get("room_id")
|
||||||
|
@ -1440,7 +1468,7 @@ pub async fn create_join_event_template_route(
|
||||||
|
|
||||||
let (_pdu, mut pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
let (_pdu, mut pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content,
|
content,
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some(body.user_id.to_string()),
|
state_key: Some(body.user_id.to_string()),
|
||||||
|
@ -1465,7 +1493,7 @@ async fn create_join_event(
|
||||||
sender_servername: &ServerName,
|
sender_servername: &ServerName,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
pdu: &RawJsonValue,
|
pdu: &RawJsonValue,
|
||||||
) -> Result<RoomState> {
|
) -> Result<create_join_event::v1::RoomState> {
|
||||||
if !services().globals.allow_federation() {
|
if !services().globals.allow_federation() {
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
@ -1587,7 +1615,7 @@ async fn create_join_event(
|
||||||
|
|
||||||
services().sending.send_pdu(servers, &pdu_id)?;
|
services().sending.send_pdu(servers, &pdu_id)?;
|
||||||
|
|
||||||
Ok(RoomState {
|
Ok(create_join_event::v1::RoomState {
|
||||||
auth_chain: auth_chain_ids
|
auth_chain: auth_chain_ids
|
||||||
.filter_map(|id| services().rooms.timeline.get_pdu_json(&id).ok().flatten())
|
.filter_map(|id| services().rooms.timeline.get_pdu_json(&id).ok().flatten())
|
||||||
.map(PduEvent::convert_to_outgoing_federation_event)
|
.map(PduEvent::convert_to_outgoing_federation_event)
|
||||||
|
@ -1628,7 +1656,18 @@ pub async fn create_join_event_v2_route(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("server is authenticated");
|
.expect("server is authenticated");
|
||||||
|
|
||||||
let room_state = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
|
let create_join_event::v1::RoomState {
|
||||||
|
auth_chain,
|
||||||
|
state,
|
||||||
|
event,
|
||||||
|
} = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
|
||||||
|
let room_state = create_join_event::v2::RoomState {
|
||||||
|
members_omitted: false,
|
||||||
|
auth_chain,
|
||||||
|
state,
|
||||||
|
event,
|
||||||
|
servers_in_room: None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(create_join_event::v2::Response { room_state })
|
Ok(create_join_event::v2::Response { room_state })
|
||||||
}
|
}
|
||||||
|
@ -1760,6 +1799,13 @@ pub async fn get_devices_route(
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if body.user_id.server_name() != services().globals.server_name() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Tried to access user from other server.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let sender_servername = body
|
let sender_servername = body
|
||||||
.sender_servername
|
.sender_servername
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -1788,12 +1834,14 @@ pub async fn get_devices_route(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
master_key: services()
|
master_key: services().users.get_master_key(None, &body.user_id, &|u| {
|
||||||
.users
|
u.server_name() == sender_servername
|
||||||
.get_master_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
})?,
|
||||||
self_signing_key: services()
|
self_signing_key: services()
|
||||||
.users
|
.users
|
||||||
.get_self_signing_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
.get_self_signing_key(None, &body.user_id, &|u| {
|
||||||
|
u.server_name() == sender_servername
|
||||||
|
})?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1832,6 +1880,13 @@ pub async fn get_profile_information_route(
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if body.user_id.server_name() != services().globals.server_name() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Tried to access user from other server.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut displayname = None;
|
let mut displayname = None;
|
||||||
let mut avatar_url = None;
|
let mut avatar_url = None;
|
||||||
let mut blurhash = None;
|
let mut blurhash = None;
|
||||||
|
@ -1868,6 +1923,17 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if body
|
||||||
|
.device_keys
|
||||||
|
.iter()
|
||||||
|
.any(|(u, _)| u.server_name() != services().globals.server_name())
|
||||||
|
{
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Tried to access user from other server.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let result = get_keys_helper(None, &body.device_keys, |u| {
|
let result = get_keys_helper(None, &body.device_keys, |u| {
|
||||||
Some(u.server_name()) == body.sender_servername.as_deref()
|
Some(u.server_name()) == body.sender_servername.as_deref()
|
||||||
})
|
})
|
||||||
|
@ -1890,6 +1956,17 @@ pub async fn claim_keys_route(
|
||||||
return Err(Error::bad_config("Federation is disabled."));
|
return Err(Error::bad_config("Federation is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if body
|
||||||
|
.one_time_keys
|
||||||
|
.iter()
|
||||||
|
.any(|(u, _)| u.server_name() != services().globals.server_name())
|
||||||
|
{
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Tried to access user from other server.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let result = claim_keys_helper(&body.one_time_keys).await?;
|
let result = claim_keys_helper(&body.one_time_keys).await?;
|
||||||
|
|
||||||
Ok(claim_keys::v1::Response {
|
Ok(claim_keys::v1::Response {
|
||||||
|
|
|
@ -28,6 +28,8 @@ pub struct Config {
|
||||||
pub db_cache_capacity_mb: f64,
|
pub db_cache_capacity_mb: f64,
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub enable_lightning_bolt: bool,
|
pub enable_lightning_bolt: bool,
|
||||||
|
#[serde(default = "true_fn")]
|
||||||
|
pub allow_check_for_updates: bool,
|
||||||
#[serde(default = "default_conduit_cache_capacity_modifier")]
|
#[serde(default = "default_conduit_cache_capacity_modifier")]
|
||||||
pub conduit_cache_capacity_modifier: f64,
|
pub conduit_cache_capacity_modifier: f64,
|
||||||
#[serde(default = "default_rocksdb_max_open_files")]
|
#[serde(default = "default_rocksdb_max_open_files")]
|
||||||
|
@ -44,6 +46,7 @@ pub struct Config {
|
||||||
pub max_fetch_prev_events: u16,
|
pub max_fetch_prev_events: u16,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
pub allow_registration: bool,
|
pub allow_registration: bool,
|
||||||
|
pub registration_token: Option<String>,
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_encryption: bool,
|
pub allow_encryption: bool,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
|
@ -54,6 +57,7 @@ pub struct Config {
|
||||||
pub allow_unstable_room_versions: bool,
|
pub allow_unstable_room_versions: bool,
|
||||||
#[serde(default = "default_default_room_version")]
|
#[serde(default = "default_default_room_version")]
|
||||||
pub default_room_version: RoomVersionId,
|
pub default_room_version: RoomVersionId,
|
||||||
|
pub well_known_client: Option<String>,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
pub allow_jaeger: bool,
|
pub allow_jaeger: bool,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
|
@ -61,7 +65,7 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub proxy: ProxyConfig,
|
pub proxy: ProxyConfig,
|
||||||
pub jwt_secret: Option<String>,
|
pub jwt_secret: Option<String>,
|
||||||
#[serde(default = "Vec::new")]
|
#[serde(default = "default_trusted_servers")]
|
||||||
pub trusted_servers: Vec<OwnedServerName>,
|
pub trusted_servers: Vec<OwnedServerName>,
|
||||||
#[serde(default = "default_log")]
|
#[serde(default = "default_log")]
|
||||||
pub log: String,
|
pub log: String,
|
||||||
|
@ -224,7 +228,7 @@ fn default_database_backend() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_db_cache_capacity_mb() -> f64 {
|
fn default_db_cache_capacity_mb() -> f64 {
|
||||||
1000.0
|
300.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_conduit_cache_capacity_modifier() -> f64 {
|
fn default_conduit_cache_capacity_modifier() -> f64 {
|
||||||
|
@ -255,6 +259,10 @@ fn default_max_fetch_prev_events() -> u16 {
|
||||||
100_u16
|
100_u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_trusted_servers() -> Vec<OwnedServerName> {
|
||||||
|
vec![OwnedServerName::try_from("matrix.org").unwrap()]
|
||||||
|
}
|
||||||
|
|
||||||
fn default_log() -> String {
|
fn default_log() -> String {
|
||||||
"warn,state_res=warn,_=off,sled=off".to_owned()
|
"warn,state_res=warn,_=off,sled=off".to_owned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,9 @@ use crate::Result;
|
||||||
/// would be used for `ordinary.onion`, `matrix.myspecial.onion`, but not `hello.myspecial.onion`.
|
/// would be used for `ordinary.onion`, `matrix.myspecial.onion`, but not `hello.myspecial.onion`.
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[derive(Default)]
|
||||||
pub enum ProxyConfig {
|
pub enum ProxyConfig {
|
||||||
|
#[default]
|
||||||
None,
|
None,
|
||||||
Global {
|
Global {
|
||||||
#[serde(deserialize_with = "crate::utils::deserialize_from_str")]
|
#[serde(deserialize_with = "crate::utils::deserialize_from_str")]
|
||||||
|
@ -48,11 +50,6 @@ impl ProxyConfig {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for ProxyConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
ProxyConfig::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct PartialProxyConfig {
|
pub struct PartialProxyConfig {
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub trait KeyValueDatabaseEngine: Send + Sync {
|
||||||
fn memory_usage(&self) -> Result<String> {
|
fn memory_usage(&self) -> Result<String> {
|
||||||
Ok("Current database engine does not support memory usage reporting.".to_owned())
|
Ok("Current database engine does not support memory usage reporting.".to_owned())
|
||||||
}
|
}
|
||||||
|
fn clear_caches(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait KvTree: Send + Sync {
|
pub trait KvTree: Send + Sync {
|
||||||
|
|
|
@ -116,7 +116,7 @@ impl KvTree for PersyTree {
|
||||||
match iter {
|
match iter {
|
||||||
Ok(iter) => Box::new(iter.filter_map(|(k, v)| {
|
Ok(iter) => Box::new(iter.filter_map(|(k, v)| {
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||||
.next()
|
.next()
|
||||||
})),
|
})),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -142,7 +142,7 @@ impl KvTree for PersyTree {
|
||||||
Ok(iter) => {
|
Ok(iter) => {
|
||||||
let map = iter.filter_map(|(k, v)| {
|
let map = iter.filter_map(|(k, v)| {
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||||
.next()
|
.next()
|
||||||
});
|
});
|
||||||
if backwards {
|
if backwards {
|
||||||
|
@ -179,7 +179,7 @@ impl KvTree for PersyTree {
|
||||||
iter.take_while(move |(k, _)| (*k).starts_with(&owned_prefix))
|
iter.take_while(move |(k, _)| (*k).starts_with(&owned_prefix))
|
||||||
.filter_map(|(k, v)| {
|
.filter_map(|(k, v)| {
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||||
.next()
|
.next()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,6 +45,17 @@ fn db_options(max_open_files: i32, rocksdb_cache: &rocksdb::Cache) -> rocksdb::O
|
||||||
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
|
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
|
||||||
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
|
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
|
||||||
|
|
||||||
|
// https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
|
||||||
|
db_opts.set_max_background_jobs(6);
|
||||||
|
db_opts.set_bytes_per_sync(1048576);
|
||||||
|
|
||||||
|
// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes#ktoleratecorruptedtailrecords
|
||||||
|
//
|
||||||
|
// Unclean shutdowns of a Matrix homeserver are likely to be fine when
|
||||||
|
// recovered in this manner as it's likely any lost information will be
|
||||||
|
// restored via federation.
|
||||||
|
db_opts.set_wal_recovery_mode(rocksdb::DBRecoveryMode::TolerateCorruptedTailRecords);
|
||||||
|
|
||||||
let prefix_extractor = rocksdb::SliceTransform::create_fixed_prefix(1);
|
let prefix_extractor = rocksdb::SliceTransform::create_fixed_prefix(1);
|
||||||
db_opts.set_prefix_extractor(prefix_extractor);
|
db_opts.set_prefix_extractor(prefix_extractor);
|
||||||
|
|
||||||
|
@ -54,7 +65,7 @@ fn db_options(max_open_files: i32, rocksdb_cache: &rocksdb::Cache) -> rocksdb::O
|
||||||
impl KeyValueDatabaseEngine for Arc<Engine> {
|
impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||||
fn open(config: &Config) -> Result<Self> {
|
fn open(config: &Config) -> Result<Self> {
|
||||||
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize;
|
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize;
|
||||||
let rocksdb_cache = rocksdb::Cache::new_lru_cache(cache_capacity_bytes).unwrap();
|
let rocksdb_cache = rocksdb::Cache::new_lru_cache(cache_capacity_bytes);
|
||||||
|
|
||||||
let db_opts = db_options(config.rocksdb_max_open_files, &rocksdb_cache);
|
let db_opts = db_options(config.rocksdb_max_open_files, &rocksdb_cache);
|
||||||
|
|
||||||
|
@ -121,6 +132,8 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||||
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_caches(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RocksDbEngineTree<'_> {
|
impl RocksDbEngineTree<'_> {
|
||||||
|
@ -161,7 +174,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
||||||
self.db
|
self.db
|
||||||
.rocks
|
.rocks
|
||||||
.iterator_cf(&self.cf(), rocksdb::IteratorMode::Start)
|
.iterator_cf(&self.cf(), rocksdb::IteratorMode::Start)
|
||||||
//.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -185,7 +198,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
//.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -226,7 +239,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
||||||
&self.cf(),
|
&self.cf(),
|
||||||
rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward),
|
rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward),
|
||||||
)
|
)
|
||||||
//.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.map(|(k, v)| (Vec::from(k), Vec::from(v)))
|
.map(|(k, v)| (Vec::from(k), Vec::from(v)))
|
||||||
.take_while(move |(k, _)| k.starts_with(&prefix)),
|
.take_while(move |(k, _)| k.starts_with(&prefix)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl Iterator for PreparedStatementIterator<'_> {
|
||||||
struct NonAliasingBox<T>(*mut T);
|
struct NonAliasingBox<T>(*mut T);
|
||||||
impl<T> Drop for NonAliasingBox<T> {
|
impl<T> Drop for NonAliasingBox<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { Box::from_raw(self.0) };
|
drop(unsafe { Box::from_raw(self.0) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ use tokio::sync::watch;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(super) struct Watchers {
|
pub(super) struct Watchers {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
|
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,13 +123,12 @@ impl service::account_data::Data for KeyValueDatabase {
|
||||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||||
.map(|(k, v)| {
|
.map(|(k, v)| {
|
||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
RoomAccountDataEventType::try_from(
|
RoomAccountDataEventType::from(
|
||||||
utils::string_from_bytes(k.rsplit(|&b| b == 0xff).next().ok_or_else(
|
utils::string_from_bytes(k.rsplit(|&b| b == 0xff).next().ok_or_else(
|
||||||
|| Error::bad_database("RoomUserData ID in db is invalid."),
|
|| Error::bad_database("RoomUserData ID in db is invalid."),
|
||||||
)?)
|
)?)
|
||||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
.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<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
||||||
Error::bad_database("Database contains invalid account data.")
|
Error::bad_database("Database contains invalid account data.")
|
||||||
})?,
|
})?,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||||
|
use lru_cache::LruCache;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||||
signatures::Ed25519KeyPair,
|
signatures::Ed25519KeyPair,
|
||||||
|
@ -11,6 +12,7 @@ use ruma::{
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||||
|
|
||||||
pub const COUNTER: &[u8] = b"c";
|
pub const COUNTER: &[u8] = b"c";
|
||||||
|
pub const LAST_CHECK_FOR_UPDATES_COUNT: &[u8] = b"u";
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl service::globals::Data for KeyValueDatabase {
|
impl service::globals::Data for KeyValueDatabase {
|
||||||
|
@ -26,6 +28,23 @@ impl service::globals::Data for KeyValueDatabase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn last_check_for_updates_id(&self) -> Result<u64> {
|
||||||
|
self.global
|
||||||
|
.get(LAST_CHECK_FOR_UPDATES_COUNT)?
|
||||||
|
.map_or(Ok(0_u64), |bytes| {
|
||||||
|
utils::u64_from_bytes(&bytes).map_err(|_| {
|
||||||
|
Error::bad_database("last check for updates count has invalid bytes.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_check_for_updates_id(&self, id: u64) -> Result<()> {
|
||||||
|
self.global
|
||||||
|
.insert(LAST_CHECK_FOR_UPDATES_COUNT, &id.to_be_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||||
let userid_bytes = user_id.as_bytes().to_vec();
|
let userid_bytes = user_id.as_bytes().to_vec();
|
||||||
let mut userid_prefix = userid_bytes.clone();
|
let mut userid_prefix = userid_bytes.clone();
|
||||||
|
@ -118,8 +137,67 @@ impl service::globals::Data for KeyValueDatabase {
|
||||||
self._db.cleanup()
|
self._db.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_usage(&self) -> Result<String> {
|
fn memory_usage(&self) -> String {
|
||||||
self._db.memory_usage()
|
let pdu_cache = self.pdu_cache.lock().unwrap().len();
|
||||||
|
let shorteventid_cache = self.shorteventid_cache.lock().unwrap().len();
|
||||||
|
let auth_chain_cache = self.auth_chain_cache.lock().unwrap().len();
|
||||||
|
let eventidshort_cache = self.eventidshort_cache.lock().unwrap().len();
|
||||||
|
let statekeyshort_cache = self.statekeyshort_cache.lock().unwrap().len();
|
||||||
|
let our_real_users_cache = self.our_real_users_cache.read().unwrap().len();
|
||||||
|
let appservice_in_room_cache = self.appservice_in_room_cache.read().unwrap().len();
|
||||||
|
let lasttimelinecount_cache = self.lasttimelinecount_cache.lock().unwrap().len();
|
||||||
|
|
||||||
|
let mut response = format!(
|
||||||
|
"\
|
||||||
|
pdu_cache: {pdu_cache}
|
||||||
|
shorteventid_cache: {shorteventid_cache}
|
||||||
|
auth_chain_cache: {auth_chain_cache}
|
||||||
|
eventidshort_cache: {eventidshort_cache}
|
||||||
|
statekeyshort_cache: {statekeyshort_cache}
|
||||||
|
our_real_users_cache: {our_real_users_cache}
|
||||||
|
appservice_in_room_cache: {appservice_in_room_cache}
|
||||||
|
lasttimelinecount_cache: {lasttimelinecount_cache}\n"
|
||||||
|
);
|
||||||
|
if let Ok(db_stats) = self._db.memory_usage() {
|
||||||
|
response += &db_stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_caches(&self, amount: u32) {
|
||||||
|
if amount > 0 {
|
||||||
|
let c = &mut *self.pdu_cache.lock().unwrap();
|
||||||
|
*c = LruCache::new(c.capacity());
|
||||||
|
}
|
||||||
|
if amount > 1 {
|
||||||
|
let c = &mut *self.shorteventid_cache.lock().unwrap();
|
||||||
|
*c = LruCache::new(c.capacity());
|
||||||
|
}
|
||||||
|
if amount > 2 {
|
||||||
|
let c = &mut *self.auth_chain_cache.lock().unwrap();
|
||||||
|
*c = LruCache::new(c.capacity());
|
||||||
|
}
|
||||||
|
if amount > 3 {
|
||||||
|
let c = &mut *self.eventidshort_cache.lock().unwrap();
|
||||||
|
*c = LruCache::new(c.capacity());
|
||||||
|
}
|
||||||
|
if amount > 4 {
|
||||||
|
let c = &mut *self.statekeyshort_cache.lock().unwrap();
|
||||||
|
*c = LruCache::new(c.capacity());
|
||||||
|
}
|
||||||
|
if amount > 5 {
|
||||||
|
let c = &mut *self.our_real_users_cache.write().unwrap();
|
||||||
|
*c = HashMap::new();
|
||||||
|
}
|
||||||
|
if amount > 6 {
|
||||||
|
let c = &mut *self.appservice_in_room_cache.write().unwrap();
|
||||||
|
*c = HashMap::new();
|
||||||
|
}
|
||||||
|
if amount > 7 {
|
||||||
|
let c = &mut *self.lasttimelinecount_cache.lock().unwrap();
|
||||||
|
*c = HashMap::new();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_keypair(&self) -> Result<Ed25519KeyPair> {
|
fn load_keypair(&self) -> Result<Ed25519KeyPair> {
|
||||||
|
@ -178,8 +256,8 @@ impl service::globals::Data for KeyValueDatabase {
|
||||||
..
|
..
|
||||||
} = new_keys;
|
} = new_keys;
|
||||||
|
|
||||||
keys.verify_keys.extend(verify_keys.into_iter());
|
keys.verify_keys.extend(verify_keys);
|
||||||
keys.old_verify_keys.extend(old_verify_keys.into_iter());
|
keys.old_verify_keys.extend(old_verify_keys);
|
||||||
|
|
||||||
self.server_signingkeys.insert(
|
self.server_signingkeys.insert(
|
||||||
origin.as_bytes(),
|
origin.as_bytes(),
|
||||||
|
|
|
@ -12,6 +12,7 @@ mod state;
|
||||||
mod state_accessor;
|
mod state_accessor;
|
||||||
mod state_cache;
|
mod state_cache;
|
||||||
mod state_compressor;
|
mod state_compressor;
|
||||||
|
mod threads;
|
||||||
mod timeline;
|
mod timeline;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,64 @@
|
||||||
use std::sync::Arc;
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use ruma::{EventId, RoomId};
|
use ruma::{EventId, RoomId, UserId};
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, Result};
|
use crate::{
|
||||||
|
database::KeyValueDatabase,
|
||||||
|
service::{self, rooms::timeline::PduCount},
|
||||||
|
services, utils, Error, PduEvent, Result,
|
||||||
|
};
|
||||||
|
|
||||||
impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
||||||
|
fn add_relation(&self, from: u64, to: u64) -> Result<()> {
|
||||||
|
let mut key = to.to_be_bytes().to_vec();
|
||||||
|
key.extend_from_slice(&from.to_be_bytes());
|
||||||
|
self.tofrom_relation.insert(&key, &[])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relations_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
shortroomid: u64,
|
||||||
|
target: u64,
|
||||||
|
until: PduCount,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||||
|
let prefix = target.to_be_bytes().to_vec();
|
||||||
|
let mut current = prefix.clone();
|
||||||
|
|
||||||
|
let count_raw = match until {
|
||||||
|
PduCount::Normal(x) => x - 1,
|
||||||
|
PduCount::Backfilled(x) => {
|
||||||
|
current.extend_from_slice(&0_u64.to_be_bytes());
|
||||||
|
u64::MAX - x - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
current.extend_from_slice(&count_raw.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(Box::new(
|
||||||
|
self.tofrom_relation
|
||||||
|
.iter_from(¤t, true)
|
||||||
|
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||||
|
.map(move |(tofrom, _data)| {
|
||||||
|
let from = utils::u64_from_bytes(&tofrom[(mem::size_of::<u64>())..])
|
||||||
|
.map_err(|_| Error::bad_database("Invalid count in tofrom_relation."))?;
|
||||||
|
|
||||||
|
let mut pduid = shortroomid.to_be_bytes().to_vec();
|
||||||
|
pduid.extend_from_slice(&from.to_be_bytes());
|
||||||
|
|
||||||
|
let mut pdu = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_from_id(&pduid)?
|
||||||
|
.ok_or_else(|| Error::bad_database("Pdu in tofrom_relation is invalid."))?;
|
||||||
|
if pdu.sender != user_id {
|
||||||
|
pdu.remove_transaction_id()?;
|
||||||
|
}
|
||||||
|
Ok((PduCount::Normal(from), pdu))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()> {
|
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()> {
|
||||||
for prev in event_ids {
|
for prev in event_ids {
|
||||||
let mut key = room_id.as_bytes().to_vec();
|
let mut key = room_id.as_bytes().to_vec();
|
||||||
|
|
|
@ -157,10 +157,9 @@ impl service::rooms::short::Data for KeyValueDatabase {
|
||||||
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
||||||
|
|
||||||
let event_type =
|
let event_type =
|
||||||
StateEventType::try_from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
StateEventType::from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
||||||
Error::bad_database("Event type in shortstatekey_statekey is invalid unicode.")
|
Error::bad_database("Event type in shortstatekey_statekey is invalid unicode.")
|
||||||
})?)
|
})?);
|
||||||
.map_err(|_| Error::bad_database("Event type in shortstatekey_statekey is invalid."))?;
|
|
||||||
|
|
||||||
let state_key = utils::string_from_bytes(statekey_bytes).map_err(|_| {
|
let state_key = utils::string_from_bytes(statekey_bytes).map_err(|_| {
|
||||||
Error::bad_database("Statekey in shortstatekey_statekey is invalid unicode.")
|
Error::bad_database("Statekey in shortstatekey_statekey is invalid unicode.")
|
||||||
|
|
|
@ -16,11 +16,11 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
||||||
.1;
|
.1;
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for compressed in full_state.into_iter() {
|
for compressed in full_state.iter() {
|
||||||
let parsed = services()
|
let parsed = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.parse_compressed_state_event(&compressed)?;
|
.parse_compressed_state_event(compressed)?;
|
||||||
result.insert(parsed.0, parsed.1);
|
result.insert(parsed.0, parsed.1);
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
@ -45,11 +45,11 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for compressed in full_state {
|
for compressed in full_state.iter() {
|
||||||
let (_, eventid) = services()
|
let (_, eventid) = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.parse_compressed_state_event(&compressed)?;
|
.parse_compressed_state_event(compressed)?;
|
||||||
if let Some(pdu) = services().rooms.timeline.get_pdu(&eventid)? {
|
if let Some(pdu) = services().rooms.timeline.get_pdu(&eventid)? {
|
||||||
result.insert(
|
result.insert(
|
||||||
(
|
(
|
||||||
|
@ -95,13 +95,13 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
||||||
.expect("there is always one layer")
|
.expect("there is always one layer")
|
||||||
.1;
|
.1;
|
||||||
Ok(full_state
|
Ok(full_state
|
||||||
.into_iter()
|
.iter()
|
||||||
.find(|bytes| bytes.starts_with(&shortstatekey.to_be_bytes()))
|
.find(|bytes| bytes.starts_with(&shortstatekey.to_be_bytes()))
|
||||||
.and_then(|compressed| {
|
.and_then(|compressed| {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.parse_compressed_state_event(&compressed)
|
.parse_compressed_state_event(compressed)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(_, id)| id)
|
.map(|(_, id)| id)
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -471,6 +471,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user was invited to.
|
/// Returns an iterator over all rooms a user was invited to.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn rooms_invited<'a>(
|
fn rooms_invited<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -549,6 +550,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user left.
|
/// Returns an iterator over all rooms a user left.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn rooms_left<'a>(
|
fn rooms_left<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashSet, mem::size_of};
|
use std::{collections::HashSet, mem::size_of, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::KeyValueDatabase,
|
database::KeyValueDatabase,
|
||||||
|
@ -37,20 +37,20 @@ impl service::rooms::state_compressor::Data for KeyValueDatabase {
|
||||||
|
|
||||||
Ok(StateDiff {
|
Ok(StateDiff {
|
||||||
parent,
|
parent,
|
||||||
added,
|
added: Arc::new(added),
|
||||||
removed,
|
removed: Arc::new(removed),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_statediff(&self, shortstatehash: u64, diff: StateDiff) -> Result<()> {
|
fn save_statediff(&self, shortstatehash: u64, diff: StateDiff) -> Result<()> {
|
||||||
let mut value = diff.parent.unwrap_or(0).to_be_bytes().to_vec();
|
let mut value = diff.parent.unwrap_or(0).to_be_bytes().to_vec();
|
||||||
for new in &diff.added {
|
for new in diff.added.iter() {
|
||||||
value.extend_from_slice(&new[..]);
|
value.extend_from_slice(&new[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diff.removed.is_empty() {
|
if !diff.removed.is_empty() {
|
||||||
value.extend_from_slice(&0_u64.to_be_bytes());
|
value.extend_from_slice(&0_u64.to_be_bytes());
|
||||||
for removed in &diff.removed {
|
for removed in diff.removed.iter() {
|
||||||
value.extend_from_slice(&removed[..]);
|
value.extend_from_slice(&removed[..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
78
src/database/key_value/rooms/threads.rs
Normal file
78
src/database/key_value/rooms/threads.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
||||||
|
|
||||||
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
||||||
|
|
||||||
|
impl service::rooms::threads::Data for KeyValueDatabase {
|
||||||
|
fn threads_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
room_id: &'a RoomId,
|
||||||
|
until: u64,
|
||||||
|
_include: &'a IncludeThreads,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> {
|
||||||
|
let prefix = services()
|
||||||
|
.rooms
|
||||||
|
.short
|
||||||
|
.get_shortroomid(room_id)?
|
||||||
|
.expect("room exists")
|
||||||
|
.to_be_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut current = prefix.clone();
|
||||||
|
current.extend_from_slice(&(until - 1).to_be_bytes());
|
||||||
|
|
||||||
|
Ok(Box::new(
|
||||||
|
self.threadid_userids
|
||||||
|
.iter_from(¤t, true)
|
||||||
|
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||||
|
.map(move |(pduid, _users)| {
|
||||||
|
let count = utils::u64_from_bytes(&pduid[(mem::size_of::<u64>())..])
|
||||||
|
.map_err(|_| Error::bad_database("Invalid pduid in threadid_userids."))?;
|
||||||
|
let mut pdu = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_from_id(&pduid)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::bad_database("Invalid pduid reference in threadid_userids")
|
||||||
|
})?;
|
||||||
|
if pdu.sender != user_id {
|
||||||
|
pdu.remove_transaction_id()?;
|
||||||
|
}
|
||||||
|
Ok((count, pdu))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()> {
|
||||||
|
let users = participants
|
||||||
|
.iter()
|
||||||
|
.map(|user| user.as_bytes())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(&[0xff][..]);
|
||||||
|
|
||||||
|
self.threadid_userids.insert(root_id, &users)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>> {
|
||||||
|
if let Some(users) = self.threadid_userids.get(root_id)? {
|
||||||
|
Ok(Some(
|
||||||
|
users
|
||||||
|
.split(|b| *b == 0xff)
|
||||||
|
.map(|bytes| {
|
||||||
|
UserId::parse(utils::string_from_bytes(bytes).map_err(|_| {
|
||||||
|
Error::bad_database("Invalid UserId bytes in threadid_userids.")
|
||||||
|
})?)
|
||||||
|
.map_err(|_| Error::bad_database("Invalid UserId in threadid_userids."))
|
||||||
|
})
|
||||||
|
.filter_map(|r| r.ok())
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,11 +39,10 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
|
|
||||||
/// Returns the `count` of this pdu's id.
|
/// Returns the `count` of this pdu's id.
|
||||||
fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<PduCount>> {
|
fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<PduCount>> {
|
||||||
Ok(self
|
self.eventid_pduid
|
||||||
.eventid_pduid
|
|
||||||
.get(event_id.as_bytes())?
|
.get(event_id.as_bytes())?
|
||||||
.map(|pdu_id| pdu_count(&pdu_id))
|
.map(|pdu_id| pdu_count(&pdu_id))
|
||||||
.transpose()?)
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the json of a pdu.
|
/// Returns the json of a pdu.
|
||||||
|
@ -80,12 +79,10 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
|
|
||||||
/// Returns the pdu's id.
|
/// Returns the pdu's id.
|
||||||
fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<Vec<u8>>> {
|
fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<Vec<u8>>> {
|
||||||
Ok(self.eventid_pduid.get(event_id.as_bytes())?)
|
self.eventid_pduid.get(event_id.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the pdu.
|
/// Returns the pdu.
|
||||||
///
|
|
||||||
/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
|
|
||||||
fn get_non_outlier_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
fn get_non_outlier_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||||
self.eventid_pduid
|
self.eventid_pduid
|
||||||
.get(event_id.as_bytes())?
|
.get(event_id.as_bytes())?
|
||||||
|
@ -198,19 +195,30 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a pdu and creates a new one with the same id.
|
/// Removes a pdu and creates a new one with the same id.
|
||||||
fn replace_pdu(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<()> {
|
fn replace_pdu(
|
||||||
|
&self,
|
||||||
|
pdu_id: &[u8],
|
||||||
|
pdu_json: &CanonicalJsonObject,
|
||||||
|
pdu: &PduEvent,
|
||||||
|
) -> Result<()> {
|
||||||
if self.pduid_pdu.get(pdu_id)?.is_some() {
|
if self.pduid_pdu.get(pdu_id)?.is_some() {
|
||||||
self.pduid_pdu.insert(
|
self.pduid_pdu.insert(
|
||||||
pdu_id,
|
pdu_id,
|
||||||
&serde_json::to_vec(pdu).expect("CanonicalJsonObject is always a valid"),
|
&serde_json::to_vec(pdu_json).expect("CanonicalJsonObject is always a valid"),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
"PDU does not exist.",
|
"PDU does not exist.",
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pdu_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&(*pdu.event_id).to_owned());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||||
|
@ -221,7 +229,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
until: PduCount,
|
until: PduCount,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||||
let (prefix, current) = count_to_id(&room_id, until, 1, true)?;
|
let (prefix, current) = count_to_id(room_id, until, 1, true)?;
|
||||||
|
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
|
|
||||||
|
@ -235,6 +243,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
if pdu.sender != user_id {
|
if pdu.sender != user_id {
|
||||||
pdu.remove_transaction_id()?;
|
pdu.remove_transaction_id()?;
|
||||||
}
|
}
|
||||||
|
pdu.add_age()?;
|
||||||
let count = pdu_count(&pdu_id)?;
|
let count = pdu_count(&pdu_id)?;
|
||||||
Ok((count, pdu))
|
Ok((count, pdu))
|
||||||
}),
|
}),
|
||||||
|
@ -247,7 +256,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
from: PduCount,
|
from: PduCount,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||||
let (prefix, current) = count_to_id(&room_id, from, 1, false)?;
|
let (prefix, current) = count_to_id(room_id, from, 1, false)?;
|
||||||
|
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
|
|
||||||
|
@ -261,6 +270,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
if pdu.sender != user_id {
|
if pdu.sender != user_id {
|
||||||
pdu.remove_transaction_id()?;
|
pdu.remove_transaction_id()?;
|
||||||
}
|
}
|
||||||
|
pdu.add_age()?;
|
||||||
let count = pdu_count(&pdu_id)?;
|
let count = pdu_count(&pdu_id)?;
|
||||||
Ok((count, pdu))
|
Ok((count, pdu))
|
||||||
}),
|
}),
|
||||||
|
@ -321,7 +331,7 @@ fn count_to_id(
|
||||||
.rooms
|
.rooms
|
||||||
.short
|
.short
|
||||||
.get_shortroomid(room_id)?
|
.get_shortroomid(room_id)?
|
||||||
.expect("room exists")
|
.ok_or_else(|| Error::bad_database("Looked for bad shortroomid in timeline"))?
|
||||||
.to_be_bytes()
|
.to_be_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let mut pdu_id = prefix.clone();
|
let mut pdu_id = prefix.clone();
|
||||||
|
|
|
@ -146,10 +146,9 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
self.userid_avatarurl
|
self.userid_avatarurl
|
||||||
.get(user_id.as_bytes())?
|
.get(user_id.as_bytes())?
|
||||||
.map(|bytes| {
|
.map(|bytes| {
|
||||||
let s = utils::string_from_bytes(&bytes)
|
utils::string_from_bytes(&bytes)
|
||||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
|
|
||||||
s.try_into()
|
|
||||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
|
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
|
||||||
|
.map(Into::into)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
@ -449,33 +448,13 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
master_key: &Raw<CrossSigningKey>,
|
master_key: &Raw<CrossSigningKey>,
|
||||||
self_signing_key: &Option<Raw<CrossSigningKey>>,
|
self_signing_key: &Option<Raw<CrossSigningKey>>,
|
||||||
user_signing_key: &Option<Raw<CrossSigningKey>>,
|
user_signing_key: &Option<Raw<CrossSigningKey>>,
|
||||||
|
notify: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// TODO: Check signatures
|
// TODO: Check signatures
|
||||||
|
|
||||||
let mut prefix = user_id.as_bytes().to_vec();
|
let mut prefix = user_id.as_bytes().to_vec();
|
||||||
prefix.push(0xff);
|
prefix.push(0xff);
|
||||||
|
|
||||||
// Master key
|
let (master_key_key, _) = self.parse_master_key(user_id, master_key)?;
|
||||||
let mut master_key_ids = master_key
|
|
||||||
.deserialize()
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?
|
|
||||||
.keys
|
|
||||||
.into_values();
|
|
||||||
|
|
||||||
let master_key_id = master_key_ids.next().ok_or(Error::BadRequest(
|
|
||||||
ErrorKind::InvalidParam,
|
|
||||||
"Master key contained no key.",
|
|
||||||
))?;
|
|
||||||
|
|
||||||
if master_key_ids.next().is_some() {
|
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::InvalidParam,
|
|
||||||
"Master key contained more than one key.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut master_key_key = prefix.clone();
|
|
||||||
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
|
||||||
|
|
||||||
self.keyid_key
|
self.keyid_key
|
||||||
.insert(&master_key_key, master_key.json().get().as_bytes())?;
|
.insert(&master_key_key, master_key.json().get().as_bytes())?;
|
||||||
|
@ -551,7 +530,9 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
.insert(user_id.as_bytes(), &user_signing_key_key)?;
|
.insert(user_id.as_bytes(), &user_signing_key_key)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if notify {
|
||||||
self.mark_device_key_update(user_id)?;
|
self.mark_device_key_update(user_id)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -592,7 +573,6 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
&serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
|
&serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO: Should we notify about this change?
|
|
||||||
self.mark_device_key_update(target_id)?;
|
self.mark_device_key_update(target_id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -691,45 +671,80 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_master_key(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
master_key: &Raw<CrossSigningKey>,
|
||||||
|
) -> Result<(Vec<u8>, CrossSigningKey)> {
|
||||||
|
let mut prefix = user_id.as_bytes().to_vec();
|
||||||
|
prefix.push(0xff);
|
||||||
|
|
||||||
|
let master_key = master_key
|
||||||
|
.deserialize()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?;
|
||||||
|
let mut master_key_ids = master_key.keys.values();
|
||||||
|
let master_key_id = master_key_ids.next().ok_or(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Master key contained no key.",
|
||||||
|
))?;
|
||||||
|
if master_key_ids.next().is_some() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Master key contained more than one key.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut master_key_key = prefix.clone();
|
||||||
|
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
||||||
|
Ok((master_key_key, master_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(
|
||||||
|
&self,
|
||||||
|
key: &[u8],
|
||||||
|
sender_user: Option<&UserId>,
|
||||||
|
user_id: &UserId,
|
||||||
|
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||||
|
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||||
|
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||||
|
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
||||||
|
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
||||||
|
clean_signatures(
|
||||||
|
&mut cross_signing_key,
|
||||||
|
sender_user,
|
||||||
|
user_id,
|
||||||
|
allowed_signatures,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Some(Raw::from_json(
|
||||||
|
serde_json::value::to_raw_value(&cross_signing_key)
|
||||||
|
.expect("Value to RawValue serialization"),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn get_master_key(
|
fn get_master_key(
|
||||||
&self,
|
&self,
|
||||||
|
sender_user: Option<&UserId>,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||||
self.userid_masterkeyid
|
self.userid_masterkeyid
|
||||||
.get(user_id.as_bytes())?
|
.get(user_id.as_bytes())?
|
||||||
.map_or(Ok(None), |key| {
|
.map_or(Ok(None), |key| {
|
||||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
self.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||||
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
|
||||||
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
|
||||||
clean_signatures(&mut cross_signing_key, user_id, allowed_signatures)?;
|
|
||||||
|
|
||||||
Ok(Some(Raw::from_json(
|
|
||||||
serde_json::value::to_raw_value(&cross_signing_key)
|
|
||||||
.expect("Value to RawValue serialization"),
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_self_signing_key(
|
fn get_self_signing_key(
|
||||||
&self,
|
&self,
|
||||||
|
sender_user: Option<&UserId>,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||||
self.userid_selfsigningkeyid
|
self.userid_selfsigningkeyid
|
||||||
.get(user_id.as_bytes())?
|
.get(user_id.as_bytes())?
|
||||||
.map_or(Ok(None), |key| {
|
.map_or(Ok(None), |key| {
|
||||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
self.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||||
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
|
||||||
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
|
||||||
clean_signatures(&mut cross_signing_key, user_id, allowed_signatures)?;
|
|
||||||
|
|
||||||
Ok(Some(Raw::from_json(
|
|
||||||
serde_json::value::to_raw_value(&cross_signing_key)
|
|
||||||
.expect("Value to RawValue serialization"),
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,6 +945,8 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KeyValueDatabase {}
|
||||||
|
|
||||||
/// Will only return with Some(username) if the password was not empty and the
|
/// Will only return with Some(username) if the password was not empty and the
|
||||||
/// username could be successfully parsed.
|
/// username could be successfully parsed.
|
||||||
/// If utils::string_from_bytes(...) returns an error that username will be skipped
|
/// If utils::string_from_bytes(...) returns an error that username will be skipped
|
||||||
|
|
|
@ -18,6 +18,7 @@ use ruma::{
|
||||||
CanonicalJsonValue, EventId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
CanonicalJsonValue, EventId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
||||||
UserId,
|
UserId,
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
fs::{self, remove_dir_all},
|
fs::{self, remove_dir_all},
|
||||||
|
@ -25,7 +26,9 @@ use std::{
|
||||||
mem::size_of,
|
mem::size_of,
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tokio::time::interval;
|
||||||
|
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
@ -80,6 +83,8 @@ pub struct KeyValueDatabase {
|
||||||
pub(super) aliasid_alias: Arc<dyn KvTree>, // AliasId = RoomId + Count
|
pub(super) aliasid_alias: Arc<dyn KvTree>, // AliasId = RoomId + Count
|
||||||
pub(super) publicroomids: Arc<dyn KvTree>,
|
pub(super) publicroomids: Arc<dyn KvTree>,
|
||||||
|
|
||||||
|
pub(super) threadid_userids: Arc<dyn KvTree>, // ThreadId = RoomId + Count
|
||||||
|
|
||||||
pub(super) tokenids: Arc<dyn KvTree>, // TokenId = ShortRoomId + Token + PduIdCount
|
pub(super) tokenids: Arc<dyn KvTree>, // TokenId = ShortRoomId + Token + PduIdCount
|
||||||
|
|
||||||
/// Participating servers in a room.
|
/// Participating servers in a room.
|
||||||
|
@ -128,6 +133,8 @@ pub struct KeyValueDatabase {
|
||||||
pub(super) eventid_outlierpdu: Arc<dyn KvTree>,
|
pub(super) eventid_outlierpdu: Arc<dyn KvTree>,
|
||||||
pub(super) softfailedeventids: Arc<dyn KvTree>,
|
pub(super) softfailedeventids: Arc<dyn KvTree>,
|
||||||
|
|
||||||
|
/// ShortEventId + ShortEventId -> ().
|
||||||
|
pub(super) tofrom_relation: Arc<dyn KvTree>,
|
||||||
/// RoomId + EventId -> Parent PDU EventId.
|
/// RoomId + EventId -> Parent PDU EventId.
|
||||||
pub(super) referencedevents: Arc<dyn KvTree>,
|
pub(super) referencedevents: Arc<dyn KvTree>,
|
||||||
|
|
||||||
|
@ -260,6 +267,10 @@ impl KeyValueDatabase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if config.registration_token == Some(String::new()) {
|
||||||
|
return Err(Error::bad_config("Registration token is empty"));
|
||||||
|
}
|
||||||
|
|
||||||
if config.max_request_size < 1024 {
|
if config.max_request_size < 1024 {
|
||||||
error!(?config.max_request_size, "Max request size is less than 1KB. Please increase it.");
|
error!(?config.max_request_size, "Max request size is less than 1KB. Please increase it.");
|
||||||
}
|
}
|
||||||
|
@ -302,6 +313,8 @@ impl KeyValueDatabase {
|
||||||
aliasid_alias: builder.open_tree("aliasid_alias")?,
|
aliasid_alias: builder.open_tree("aliasid_alias")?,
|
||||||
publicroomids: builder.open_tree("publicroomids")?,
|
publicroomids: builder.open_tree("publicroomids")?,
|
||||||
|
|
||||||
|
threadid_userids: builder.open_tree("threadid_userids")?,
|
||||||
|
|
||||||
tokenids: builder.open_tree("tokenids")?,
|
tokenids: builder.open_tree("tokenids")?,
|
||||||
|
|
||||||
roomserverids: builder.open_tree("roomserverids")?,
|
roomserverids: builder.open_tree("roomserverids")?,
|
||||||
|
@ -342,6 +355,7 @@ impl KeyValueDatabase {
|
||||||
eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
|
eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
|
||||||
softfailedeventids: builder.open_tree("softfailedeventids")?,
|
softfailedeventids: builder.open_tree("softfailedeventids")?,
|
||||||
|
|
||||||
|
tofrom_relation: builder.open_tree("tofrom_relation")?,
|
||||||
referencedevents: builder.open_tree("referencedevents")?,
|
referencedevents: builder.open_tree("referencedevents")?,
|
||||||
roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
|
roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
|
||||||
roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
|
roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
|
||||||
|
@ -411,7 +425,7 @@ impl KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the database has any data, perform data migrations before starting
|
// If the database has any data, perform data migrations before starting
|
||||||
let latest_database_version = 12;
|
let latest_database_version = 13;
|
||||||
|
|
||||||
if services().users.count()? > 0 {
|
if services().users.count()? > 0 {
|
||||||
// MIGRATIONS
|
// MIGRATIONS
|
||||||
|
@ -580,8 +594,8 @@ impl KeyValueDatabase {
|
||||||
|
|
||||||
services().rooms.state_compressor.save_state_from_diff(
|
services().rooms.state_compressor.save_state_from_diff(
|
||||||
current_sstatehash,
|
current_sstatehash,
|
||||||
statediffnew,
|
Arc::new(statediffnew),
|
||||||
statediffremoved,
|
Arc::new(statediffremoved),
|
||||||
2, // every state change is 2 event changes on average
|
2, // every state change is 2 event changes on average
|
||||||
states_parents,
|
states_parents,
|
||||||
)?;
|
)?;
|
||||||
|
@ -838,7 +852,9 @@ impl KeyValueDatabase {
|
||||||
if rule.is_some() {
|
if rule.is_some() {
|
||||||
let mut rule = rule.unwrap().clone();
|
let mut rule = rule.unwrap().clone();
|
||||||
rule.rule_id = content_rule_transformation[1].to_owned();
|
rule.rule_id = content_rule_transformation[1].to_owned();
|
||||||
rules_list.content.remove(content_rule_transformation[0]);
|
rules_list
|
||||||
|
.content
|
||||||
|
.shift_remove(content_rule_transformation[0]);
|
||||||
rules_list.content.insert(rule);
|
rules_list.content.insert(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -861,7 +877,7 @@ impl KeyValueDatabase {
|
||||||
if let Some(rule) = rule {
|
if let Some(rule) = rule {
|
||||||
let mut rule = rule.clone();
|
let mut rule = rule.clone();
|
||||||
rule.rule_id = transformation[1].to_owned();
|
rule.rule_id = transformation[1].to_owned();
|
||||||
rules_list.underride.remove(transformation[0]);
|
rules_list.underride.shift_remove(transformation[0]);
|
||||||
rules_list.underride.insert(rule);
|
rules_list.underride.insert(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -880,6 +896,52 @@ impl KeyValueDatabase {
|
||||||
warn!("Migration: 11 -> 12 finished");
|
warn!("Migration: 11 -> 12 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This migration can be reused as-is anytime the server-default rules are updated.
|
||||||
|
if services().globals.database_version()? < 13 {
|
||||||
|
for username in services().users.list_local_users()? {
|
||||||
|
let user = match UserId::parse_with_server_name(
|
||||||
|
username.clone(),
|
||||||
|
services().globals.server_name(),
|
||||||
|
) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Invalid username {username}: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_rules_list = services()
|
||||||
|
.account_data
|
||||||
|
.get(
|
||||||
|
None,
|
||||||
|
&user,
|
||||||
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.expect("Username is invalid");
|
||||||
|
|
||||||
|
let mut account_data =
|
||||||
|
serde_json::from_str::<PushRulesEvent>(raw_rules_list.get()).unwrap();
|
||||||
|
|
||||||
|
let user_default_rules = ruma::push::Ruleset::server_default(&user);
|
||||||
|
account_data
|
||||||
|
.content
|
||||||
|
.global
|
||||||
|
.update_with_server_default(user_default_rules);
|
||||||
|
|
||||||
|
services().account_data.update(
|
||||||
|
None,
|
||||||
|
&user,
|
||||||
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||||
|
&serde_json::to_value(account_data).expect("to json value always works"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services().globals.bump_database_version(13)?;
|
||||||
|
|
||||||
|
warn!("Migration: 12 -> 13 finished");
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
services().globals.database_version().unwrap(),
|
services().globals.database_version().unwrap(),
|
||||||
latest_database_version
|
latest_database_version
|
||||||
|
@ -929,6 +991,9 @@ impl KeyValueDatabase {
|
||||||
services().sending.start_handler();
|
services().sending.start_handler();
|
||||||
|
|
||||||
Self::start_cleanup_task().await;
|
Self::start_cleanup_task().await;
|
||||||
|
if services().globals.allow_check_for_updates() {
|
||||||
|
Self::start_check_for_updates_task();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -945,9 +1010,61 @@ impl KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn start_cleanup_task() {
|
pub fn start_check_for_updates_task() {
|
||||||
use tokio::time::interval;
|
tokio::spawn(async move {
|
||||||
|
let timer_interval = Duration::from_secs(60 * 60);
|
||||||
|
let mut i = interval(timer_interval);
|
||||||
|
loop {
|
||||||
|
i.tick().await;
|
||||||
|
let _ = Self::try_handle_updates().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_handle_updates() -> Result<()> {
|
||||||
|
let response = services()
|
||||||
|
.globals
|
||||||
|
.default_client()
|
||||||
|
.get("https://conduit.rs/check-for-updates/stable")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CheckForUpdatesResponseEntry {
|
||||||
|
id: u64,
|
||||||
|
date: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CheckForUpdatesResponse {
|
||||||
|
updates: Vec<CheckForUpdatesResponseEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = serde_json::from_str::<CheckForUpdatesResponse>(&response.text().await?)
|
||||||
|
.map_err(|_| Error::BadServerResponse("Bad version check response"))?;
|
||||||
|
|
||||||
|
let mut last_update_id = services().globals.last_check_for_updates_id()?;
|
||||||
|
for update in response.updates {
|
||||||
|
last_update_id = last_update_id.max(update.id);
|
||||||
|
if update.id > services().globals.last_check_for_updates_id()? {
|
||||||
|
println!("{}", update.message);
|
||||||
|
services()
|
||||||
|
.admin
|
||||||
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
|
"@room: The following is a message from the Conduit developers. It was sent on '{}':\n\n{}",
|
||||||
|
update.date, update.message
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
services()
|
||||||
|
.globals
|
||||||
|
.update_check_for_updates_id(last_update_id)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn start_cleanup_task() {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
#![warn(
|
|
||||||
rust_2018_idioms,
|
|
||||||
unused_qualifications,
|
|
||||||
clippy::cloned_instead_of_copied,
|
|
||||||
clippy::str_to_string
|
|
||||||
)]
|
|
||||||
#![allow(clippy::suspicious_else_formatting)]
|
|
||||||
#![deny(clippy::dbg_macro)]
|
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
mod config;
|
mod config;
|
||||||
mod database;
|
mod database;
|
||||||
|
|
95
src/main.rs
95
src/main.rs
|
@ -1,17 +1,7 @@
|
||||||
#![warn(
|
use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
|
||||||
rust_2018_idioms,
|
|
||||||
unused_qualifications,
|
|
||||||
clippy::cloned_instead_of_copied,
|
|
||||||
clippy::str_to_string
|
|
||||||
)]
|
|
||||||
#![allow(clippy::suspicious_else_formatting)]
|
|
||||||
#![deny(clippy::dbg_macro)]
|
|
||||||
|
|
||||||
use std::{future::Future, io, net::SocketAddr, time::Duration};
|
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{DefaultBodyLimit, FromRequest, MatchedPath},
|
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||||
handler::Handler,
|
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, on, MethodFilter},
|
routing::{get, on, MethodFilter},
|
||||||
Router,
|
Router,
|
||||||
|
@ -40,7 +30,7 @@ use tower_http::{
|
||||||
trace::TraceLayer,
|
trace::TraceLayer,
|
||||||
ServiceBuilderExt as _,
|
ServiceBuilderExt as _,
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
|
|
||||||
pub use conduit::*; // Re-export everything from the library crate
|
pub use conduit::*; // Re-export everything from the library crate
|
||||||
|
@ -54,7 +44,7 @@ static GLOBAL: Jemalloc = Jemalloc;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Initialize DB
|
// Initialize config
|
||||||
let raw_config =
|
let raw_config =
|
||||||
Figment::new()
|
Figment::new()
|
||||||
.merge(
|
.merge(
|
||||||
|
@ -75,6 +65,8 @@ async fn main() {
|
||||||
|
|
||||||
config.warn_deprecated();
|
config.warn_deprecated();
|
||||||
|
|
||||||
|
let log = format!("{},ruma_state_res=error,_=off,sled=off", config.log);
|
||||||
|
|
||||||
if config.allow_jaeger {
|
if config.allow_jaeger {
|
||||||
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
|
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
|
||||||
let tracer = opentelemetry_jaeger::new_agent_pipeline()
|
let tracer = opentelemetry_jaeger::new_agent_pipeline()
|
||||||
|
@ -84,7 +76,7 @@ async fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||||
|
|
||||||
let filter_layer = match EnvFilter::try_new(&config.log) {
|
let filter_layer = match EnvFilter::try_new(&log) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
@ -111,7 +103,7 @@ async fn main() {
|
||||||
} else {
|
} else {
|
||||||
let registry = tracing_subscriber::Registry::default();
|
let registry = tracing_subscriber::Registry::default();
|
||||||
let fmt_layer = tracing_subscriber::fmt::Layer::new();
|
let fmt_layer = tracing_subscriber::fmt::Layer::new();
|
||||||
let filter_layer = match EnvFilter::try_new(&config.log) {
|
let filter_layer = match EnvFilter::try_new(&log) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("It looks like your config is invalid. The following error occured while parsing it: {e}");
|
eprintln!("It looks like your config is invalid. The following error occured while parsing it: {e}");
|
||||||
|
@ -123,6 +115,16 @@ async fn main() {
|
||||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is needed for opening lots of file descriptors, which tends to
|
||||||
|
// happen more often when using RocksDB and making lots of federation
|
||||||
|
// connections at startup. The soft limit is usually 1024, and the hard
|
||||||
|
// limit is usually 512000; I've personally seen it hit >2000.
|
||||||
|
//
|
||||||
|
// * https://www.freedesktop.org/software/systemd/man/systemd.exec.html#id-1.12.2.1.17.6
|
||||||
|
// * https://github.com/systemd/systemd/commit/0abf94923b4a95a7d89bc526efc84e7ca2b71741
|
||||||
|
#[cfg(unix)]
|
||||||
|
maximize_fd_limit().expect("should be able to increase the soft limit to the hard limit");
|
||||||
|
|
||||||
info!("Loading database");
|
info!("Loading database");
|
||||||
if let Err(error) = KeyValueDatabase::load_or_create(config).await {
|
if let Err(error) = KeyValueDatabase::load_or_create(config).await {
|
||||||
error!(?error, "The database couldn't be loaded or created");
|
error!(?error, "The database couldn't be loaded or created");
|
||||||
|
@ -147,6 +149,7 @@ async fn run_server() -> io::Result<()> {
|
||||||
|
|
||||||
let middlewares = ServiceBuilder::new()
|
let middlewares = ServiceBuilder::new()
|
||||||
.sensitive_headers([header::AUTHORIZATION])
|
.sensitive_headers([header::AUTHORIZATION])
|
||||||
|
.layer(axum::middleware::from_fn(spawn_task))
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http().make_span_with(|request: &http::Request<_>| {
|
TraceLayer::new_for_http().make_span_with(|request: &http::Request<_>| {
|
||||||
let path = if let Some(path) = request.extensions().get::<MatchedPath>() {
|
let path = if let Some(path) = request.extensions().get::<MatchedPath>() {
|
||||||
|
@ -158,7 +161,6 @@ async fn run_server() -> io::Result<()> {
|
||||||
tracing::info_span!("http_request", %path)
|
tracing::info_span!("http_request", %path)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.compression()
|
|
||||||
.layer(axum::middleware::from_fn(unrecognized_method))
|
.layer(axum::middleware::from_fn(unrecognized_method))
|
||||||
.layer(
|
.layer(
|
||||||
CorsLayer::new()
|
CorsLayer::new()
|
||||||
|
@ -211,17 +213,22 @@ async fn run_server() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On shutdown
|
|
||||||
info!(target: "shutdown-sync", "Received shutdown notification, notifying sync helpers...");
|
|
||||||
services().globals.rotate.fire();
|
|
||||||
|
|
||||||
#[cfg(feature = "systemd")]
|
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unrecognized_method<B>(
|
async fn spawn_task<B: Send + 'static>(
|
||||||
|
req: axum::http::Request<B>,
|
||||||
|
next: axum::middleware::Next<B>,
|
||||||
|
) -> std::result::Result<axum::response::Response, StatusCode> {
|
||||||
|
if services().globals.shutdown.load(atomic::Ordering::Relaxed) {
|
||||||
|
return Err(StatusCode::SERVICE_UNAVAILABLE);
|
||||||
|
}
|
||||||
|
tokio::spawn(next.run(req))
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unrecognized_method<B: Send>(
|
||||||
req: axum::http::Request<B>,
|
req: axum::http::Request<B>,
|
||||||
next: axum::middleware::Next<B>,
|
next: axum::middleware::Next<B>,
|
||||||
) -> std::result::Result<axum::response::Response, StatusCode> {
|
) -> std::result::Result<axum::response::Response, StatusCode> {
|
||||||
|
@ -352,6 +359,7 @@ fn routes() -> Router {
|
||||||
.put(client_server::send_state_event_for_empty_key_route),
|
.put(client_server::send_state_event_for_empty_key_route),
|
||||||
)
|
)
|
||||||
.ruma_route(client_server::sync_events_route)
|
.ruma_route(client_server::sync_events_route)
|
||||||
|
.ruma_route(client_server::sync_events_v4_route)
|
||||||
.ruma_route(client_server::get_context_route)
|
.ruma_route(client_server::get_context_route)
|
||||||
.ruma_route(client_server::get_message_events_route)
|
.ruma_route(client_server::get_message_events_route)
|
||||||
.ruma_route(client_server::search_events_route)
|
.ruma_route(client_server::search_events_route)
|
||||||
|
@ -377,6 +385,11 @@ fn routes() -> Router {
|
||||||
.ruma_route(client_server::set_pushers_route)
|
.ruma_route(client_server::set_pushers_route)
|
||||||
// .ruma_route(client_server::third_party_route)
|
// .ruma_route(client_server::third_party_route)
|
||||||
.ruma_route(client_server::upgrade_room_route)
|
.ruma_route(client_server::upgrade_room_route)
|
||||||
|
.ruma_route(client_server::get_threads_route)
|
||||||
|
.ruma_route(client_server::get_relating_events_with_rel_type_and_event_type_route)
|
||||||
|
.ruma_route(client_server::get_relating_events_with_rel_type_route)
|
||||||
|
.ruma_route(client_server::get_relating_events_route)
|
||||||
|
.ruma_route(client_server::get_hierarchy_route)
|
||||||
.ruma_route(server_server::get_server_version_route)
|
.ruma_route(server_server::get_server_version_route)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/key/v2/server",
|
"/_matrix/key/v2/server",
|
||||||
|
@ -412,7 +425,8 @@ fn routes() -> Router {
|
||||||
"/_matrix/client/v3/rooms/:room_id/initialSync",
|
"/_matrix/client/v3/rooms/:room_id/initialSync",
|
||||||
get(initial_sync),
|
get(initial_sync),
|
||||||
)
|
)
|
||||||
.fallback(not_found.into_service())
|
.route("/", get(it_works))
|
||||||
|
.fallback(not_found)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn shutdown_signal(handle: ServerHandle) {
|
async fn shutdown_signal(handle: ServerHandle) {
|
||||||
|
@ -442,6 +456,11 @@ async fn shutdown_signal(handle: ServerHandle) {
|
||||||
|
|
||||||
warn!("Received {}, shutting down...", sig);
|
warn!("Received {}, shutting down...", sig);
|
||||||
handle.graceful_shutdown(Some(Duration::from_secs(30)));
|
handle.graceful_shutdown(Some(Duration::from_secs(30)));
|
||||||
|
|
||||||
|
services().globals.shutdown();
|
||||||
|
|
||||||
|
#[cfg(feature = "systemd")]
|
||||||
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn not_found(uri: Uri) -> impl IntoResponse {
|
async fn not_found(uri: Uri) -> impl IntoResponse {
|
||||||
|
@ -456,6 +475,10 @@ async fn initial_sync(_uri: Uri) -> impl IntoResponse {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn it_works() -> &'static str {
|
||||||
|
"Hello from Conduit!"
|
||||||
|
}
|
||||||
|
|
||||||
trait RouterExt {
|
trait RouterExt {
|
||||||
fn ruma_route<H, T>(self, handler: H) -> Self
|
fn ruma_route<H, T>(self, handler: H) -> Self
|
||||||
where
|
where
|
||||||
|
@ -491,7 +514,7 @@ macro_rules! impl_ruma_handler {
|
||||||
Fut: Future<Output = Result<Req::OutgoingResponse, E>>
|
Fut: Future<Output = Result<Req::OutgoingResponse, E>>
|
||||||
+ Send,
|
+ Send,
|
||||||
E: IntoResponse,
|
E: IntoResponse,
|
||||||
$( $ty: FromRequest<axum::body::Body> + Send + 'static, )*
|
$( $ty: FromRequestParts<()> + Send + 'static, )*
|
||||||
{
|
{
|
||||||
fn add_to_router(self, mut router: Router) -> Router {
|
fn add_to_router(self, mut router: Router) -> Router {
|
||||||
let meta = Req::METADATA;
|
let meta = Req::METADATA;
|
||||||
|
@ -534,3 +557,21 @@ fn method_to_filter(method: Method) -> MethodFilter {
|
||||||
m => panic!("Unsupported HTTP method: {m:?}"),
|
m => panic!("Unsupported HTTP method: {m:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[tracing::instrument(err)]
|
||||||
|
fn maximize_fd_limit() -> Result<(), nix::errno::Errno> {
|
||||||
|
use nix::sys::resource::{getrlimit, setrlimit, Resource};
|
||||||
|
|
||||||
|
let res = Resource::RLIMIT_NOFILE;
|
||||||
|
|
||||||
|
let (soft_limit, hard_limit) = getrlimit(res)?;
|
||||||
|
|
||||||
|
debug!("Current nofile soft limit: {soft_limit}");
|
||||||
|
|
||||||
|
setrlimit(res, hard_limit, hard_limit)?;
|
||||||
|
|
||||||
|
debug!("Increased nofile soft limit to {hard_limit}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
sync::Arc,
|
sync::{Arc, RwLock},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use ruma::{
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
topic::RoomTopicEventContent,
|
topic::RoomTopicEventContent,
|
||||||
},
|
},
|
||||||
RoomEventType,
|
TimelineEventType,
|
||||||
},
|
},
|
||||||
EventId, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
|
EventId, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
|
@ -50,7 +50,7 @@ enum AdminCommand {
|
||||||
/// Registering a new bridge using the ID of an existing bridge will replace
|
/// Registering a new bridge using the ID of an existing bridge will replace
|
||||||
/// the old one.
|
/// the old one.
|
||||||
///
|
///
|
||||||
/// [commandbody]
|
/// [commandbody]()
|
||||||
/// # ```
|
/// # ```
|
||||||
/// # yaml content here
|
/// # yaml content here
|
||||||
/// # ```
|
/// # ```
|
||||||
|
@ -96,7 +96,7 @@ enum AdminCommand {
|
||||||
/// Removing a mass amount of users from a room may cause a significant amount of leave events.
|
/// Removing a mass amount of users from a room may cause a significant amount of leave events.
|
||||||
/// The time to leave rooms may depend significantly on joined rooms and servers.
|
/// The time to leave rooms may depend significantly on joined rooms and servers.
|
||||||
///
|
///
|
||||||
/// [commandbody]
|
/// [commandbody]()
|
||||||
/// # ```
|
/// # ```
|
||||||
/// # User list here
|
/// # User list here
|
||||||
/// # ```
|
/// # ```
|
||||||
|
@ -121,7 +121,7 @@ enum AdminCommand {
|
||||||
/// The PDU event is only checked for validity and is not added to the
|
/// The PDU event is only checked for validity and is not added to the
|
||||||
/// database.
|
/// database.
|
||||||
///
|
///
|
||||||
/// [commandbody]
|
/// [commandbody]()
|
||||||
/// # ```
|
/// # ```
|
||||||
/// # PDU json content here
|
/// # PDU json content here
|
||||||
/// # ```
|
/// # ```
|
||||||
|
@ -134,7 +134,13 @@ enum AdminCommand {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Print database memory usage statistics
|
/// Print database memory usage statistics
|
||||||
DatabaseMemoryUsage,
|
MemoryUsage,
|
||||||
|
|
||||||
|
/// Clears all of Conduit's database caches with index smaller than the amount
|
||||||
|
ClearDatabaseCaches { amount: u32 },
|
||||||
|
|
||||||
|
/// Clears all of Conduit's service caches with index smaller than the amount
|
||||||
|
ClearServiceCaches { amount: u32 },
|
||||||
|
|
||||||
/// Show configuration values
|
/// Show configuration values
|
||||||
ShowConfig,
|
ShowConfig,
|
||||||
|
@ -157,6 +163,20 @@ enum AdminCommand {
|
||||||
DisableRoom { room_id: Box<RoomId> },
|
DisableRoom { room_id: Box<RoomId> },
|
||||||
/// Enables incoming federation handling for a room again.
|
/// Enables incoming federation handling for a room again.
|
||||||
EnableRoom { room_id: Box<RoomId> },
|
EnableRoom { room_id: Box<RoomId> },
|
||||||
|
|
||||||
|
/// Verify json signatures
|
||||||
|
/// [commandbody]()
|
||||||
|
/// # ```
|
||||||
|
/// # json here
|
||||||
|
/// # ```
|
||||||
|
SignJson,
|
||||||
|
|
||||||
|
/// Verify json signatures
|
||||||
|
/// [commandbody]()
|
||||||
|
/// # ```
|
||||||
|
/// # json here
|
||||||
|
/// # ```
|
||||||
|
VerifyJson,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -212,7 +232,7 @@ impl Service {
|
||||||
.timeline
|
.timeline
|
||||||
.build_and_append_pdu(
|
.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMessage,
|
event_type: TimelineEventType::RoomMessage,
|
||||||
content: to_raw_value(&message)
|
content: to_raw_value(&message)
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -267,7 +287,7 @@ impl Service {
|
||||||
|
|
||||||
// Parse and process a message from the admin room
|
// Parse and process a message from the admin room
|
||||||
async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent {
|
async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent {
|
||||||
let mut lines = room_message.lines();
|
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
|
||||||
let command_line = lines.next().expect("each string has at least one line");
|
let command_line = lines.next().expect("each string has at least one line");
|
||||||
let body: Vec<_> = lines.collect();
|
let body: Vec<_> = lines.collect();
|
||||||
|
|
||||||
|
@ -531,12 +551,24 @@ impl Service {
|
||||||
None => RoomMessageEventContent::text_plain("PDU not found."),
|
None => RoomMessageEventContent::text_plain("PDU not found."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::DatabaseMemoryUsage => match services().globals.db.memory_usage() {
|
AdminCommand::MemoryUsage => {
|
||||||
Ok(response) => RoomMessageEventContent::text_plain(response),
|
let response1 = services().memory_usage();
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
let response2 = services().globals.db.memory_usage();
|
||||||
"Failed to get database memory usage: {e}"
|
|
||||||
)),
|
RoomMessageEventContent::text_plain(format!(
|
||||||
},
|
"Services:\n{response1}\n\nDatabase:\n{response2}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
AdminCommand::ClearDatabaseCaches { amount } => {
|
||||||
|
services().globals.db.clear_caches(amount);
|
||||||
|
|
||||||
|
RoomMessageEventContent::text_plain("Done.")
|
||||||
|
}
|
||||||
|
AdminCommand::ClearServiceCaches { amount } => {
|
||||||
|
services().clear_caches(amount);
|
||||||
|
|
||||||
|
RoomMessageEventContent::text_plain("Done.")
|
||||||
|
}
|
||||||
AdminCommand::ShowConfig => {
|
AdminCommand::ShowConfig => {
|
||||||
// Construct and send the response
|
// Construct and send the response
|
||||||
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
||||||
|
@ -736,6 +768,60 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AdminCommand::SignJson => {
|
||||||
|
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||||
|
{
|
||||||
|
let string = body[1..body.len() - 1].join("\n");
|
||||||
|
match serde_json::from_str(&string) {
|
||||||
|
Ok(mut value) => {
|
||||||
|
ruma::signatures::sign_json(
|
||||||
|
services().globals.server_name().as_str(),
|
||||||
|
services().globals.keypair(),
|
||||||
|
&mut value,
|
||||||
|
)
|
||||||
|
.expect("our request json is what ruma expects");
|
||||||
|
let json_text = serde_json::to_string_pretty(&value)
|
||||||
|
.expect("canonical json is valid json");
|
||||||
|
RoomMessageEventContent::text_plain(json_text)
|
||||||
|
}
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoomMessageEventContent::text_plain(
|
||||||
|
"Expected code block in command body. Add --help for details.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AdminCommand::VerifyJson => {
|
||||||
|
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||||
|
{
|
||||||
|
let string = body[1..body.len() - 1].join("\n");
|
||||||
|
match serde_json::from_str(&string) {
|
||||||
|
Ok(value) => {
|
||||||
|
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||||
|
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.event_handler
|
||||||
|
.fetch_required_signing_keys(&value, &pub_key_map)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let pub_key_map = pub_key_map.read().unwrap();
|
||||||
|
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||||
|
Ok(_) => RoomMessageEventContent::text_plain("Signature correct"),
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Signature verification failed: {e}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoomMessageEventContent::text_plain(
|
||||||
|
"Expected code block in command body. Add --help for details.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(reply_message_content)
|
Ok(reply_message_content)
|
||||||
|
@ -772,12 +858,15 @@ impl Service {
|
||||||
.expect("Regex compilation should not fail");
|
.expect("Regex compilation should not fail");
|
||||||
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
||||||
|
|
||||||
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
|
// Look for a `[commandbody]()` tag. If it exists, use all lines below it that
|
||||||
// start with a `#` in the USAGE section.
|
// start with a `#` in the USAGE section.
|
||||||
let mut text_lines: Vec<&str> = text.lines().collect();
|
let mut text_lines: Vec<&str> = text.lines().collect();
|
||||||
let mut command_body = String::new();
|
let mut command_body = String::new();
|
||||||
|
|
||||||
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
|
if let Some(line_index) = text_lines
|
||||||
|
.iter()
|
||||||
|
.position(|line| *line == "[commandbody]()")
|
||||||
|
{
|
||||||
text_lines.remove(line_index);
|
text_lines.remove(line_index);
|
||||||
|
|
||||||
while text_lines
|
while text_lines
|
||||||
|
@ -846,7 +935,7 @@ impl Service {
|
||||||
|
|
||||||
services().users.create(&conduit_user, None)?;
|
services().users.create(&conduit_user, None)?;
|
||||||
|
|
||||||
let mut content = RoomCreateEventContent::new(conduit_user.clone());
|
let mut content = RoomCreateEventContent::new_v1(conduit_user.clone());
|
||||||
content.federate = true;
|
content.federate = true;
|
||||||
content.predecessor = None;
|
content.predecessor = None;
|
||||||
content.room_version = services().globals.default_room_version();
|
content.room_version = services().globals.default_room_version();
|
||||||
|
@ -854,7 +943,7 @@ impl Service {
|
||||||
// 1. The room create event
|
// 1. The room create event
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomCreate,
|
event_type: TimelineEventType::RoomCreate,
|
||||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some("".to_owned()),
|
state_key: Some("".to_owned()),
|
||||||
|
@ -868,7 +957,7 @@ impl Service {
|
||||||
// 2. Make conduit bot join
|
// 2. Make conduit bot join
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Join,
|
membership: MembershipState::Join,
|
||||||
displayname: None,
|
displayname: None,
|
||||||
|
@ -895,7 +984,7 @@ impl Service {
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
users,
|
users,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -913,7 +1002,7 @@ impl Service {
|
||||||
// 4.1 Join Rules
|
// 4.1 Join Rules
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomJoinRules,
|
event_type: TimelineEventType::RoomJoinRules,
|
||||||
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -928,7 +1017,7 @@ impl Service {
|
||||||
// 4.2 History Visibility
|
// 4.2 History Visibility
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomHistoryVisibility,
|
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||||
HistoryVisibility::Shared,
|
HistoryVisibility::Shared,
|
||||||
))
|
))
|
||||||
|
@ -945,7 +1034,7 @@ impl Service {
|
||||||
// 4.3 Guest Access
|
// 4.3 Guest Access
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomGuestAccess,
|
event_type: TimelineEventType::RoomGuestAccess,
|
||||||
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
|
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -961,8 +1050,8 @@ impl Service {
|
||||||
let room_name = format!("{} Admin Room", services().globals.server_name());
|
let room_name = format!("{} Admin Room", services().globals.server_name());
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomName,
|
event_type: TimelineEventType::RoomName,
|
||||||
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
|
content: to_raw_value(&RoomNameEventContent::new(room_name))
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
state_key: Some("".to_owned()),
|
state_key: Some("".to_owned()),
|
||||||
|
@ -975,7 +1064,7 @@ impl Service {
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomTopic,
|
event_type: TimelineEventType::RoomTopic,
|
||||||
content: to_raw_value(&RoomTopicEventContent {
|
content: to_raw_value(&RoomTopicEventContent {
|
||||||
topic: format!("Manage {}", services().globals.server_name()),
|
topic: format!("Manage {}", services().globals.server_name()),
|
||||||
})
|
})
|
||||||
|
@ -996,7 +1085,7 @@ impl Service {
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomCanonicalAlias,
|
event_type: TimelineEventType::RoomCanonicalAlias,
|
||||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||||
alias: Some(alias.clone()),
|
alias: Some(alias.clone()),
|
||||||
alt_aliases: Vec::new(),
|
alt_aliases: Vec::new(),
|
||||||
|
@ -1053,7 +1142,7 @@ impl Service {
|
||||||
// Invite and join the real user
|
// Invite and join the real user
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Invite,
|
membership: MembershipState::Invite,
|
||||||
displayname: None,
|
displayname: None,
|
||||||
|
@ -1075,7 +1164,7 @@ impl Service {
|
||||||
)?;
|
)?;
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMember,
|
event_type: TimelineEventType::RoomMember,
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
membership: MembershipState::Join,
|
membership: MembershipState::Join,
|
||||||
displayname: Some(displayname),
|
displayname: Some(displayname),
|
||||||
|
@ -1103,7 +1192,7 @@ impl Service {
|
||||||
|
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
users,
|
users,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1121,7 +1210,7 @@ impl Service {
|
||||||
// Send welcome message
|
// Send welcome message
|
||||||
services().rooms.timeline.build_and_append_pdu(
|
services().rooms.timeline.build_and_append_pdu(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: RoomEventType::RoomMessage,
|
event_type: TimelineEventType::RoomMessage,
|
||||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||||
format!("## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`", services().globals.server_name()),
|
format!("## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`", services().globals.server_name()),
|
||||||
format!("<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n", services().globals.server_name()),
|
format!("<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n", services().globals.server_name()),
|
||||||
|
|
|
@ -13,9 +13,12 @@ use crate::Result;
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn next_count(&self) -> Result<u64>;
|
fn next_count(&self) -> Result<u64>;
|
||||||
fn current_count(&self) -> Result<u64>;
|
fn current_count(&self) -> Result<u64>;
|
||||||
|
fn last_check_for_updates_id(&self) -> Result<u64>;
|
||||||
|
fn update_check_for_updates_id(&self, id: u64) -> Result<()>;
|
||||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>;
|
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>;
|
||||||
fn cleanup(&self) -> Result<()>;
|
fn cleanup(&self) -> Result<()>;
|
||||||
fn memory_usage(&self) -> Result<String>;
|
fn memory_usage(&self) -> String;
|
||||||
|
fn clear_caches(&self, amount: u32);
|
||||||
fn load_keypair(&self) -> Result<Ed25519KeyPair>;
|
fn load_keypair(&self) -> Result<Ed25519KeyPair>;
|
||||||
fn remove_keypair(&self) -> Result<()>;
|
fn remove_keypair(&self) -> Result<()>;
|
||||||
fn add_signing_key(
|
fn add_signing_key(
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
mod data;
|
mod data;
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId,
|
serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||||
|
OwnedServerSigningKeyId, OwnedUserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::api::server_server::FedDest;
|
use crate::api::server_server::FedDest;
|
||||||
|
|
||||||
use crate::{Config, Error, Result};
|
use crate::{services, Config, Error, Result};
|
||||||
|
use futures_util::FutureExt;
|
||||||
|
use hyper::{
|
||||||
|
client::connect::dns::{GaiResolver, Name},
|
||||||
|
service::Service as HyperService,
|
||||||
|
};
|
||||||
|
use reqwest::dns::{Addrs, Resolve, Resolving};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::sync::sync_events,
|
client::sync::sync_events,
|
||||||
|
@ -16,17 +23,24 @@ use ruma::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
|
error::Error as StdError,
|
||||||
fs,
|
fs,
|
||||||
future::Future,
|
future::{self, Future},
|
||||||
|
iter,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{
|
||||||
|
atomic::{self, AtomicBool},
|
||||||
|
Arc, Mutex, RwLock,
|
||||||
|
},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tokio::sync::{broadcast, watch::Receiver, Mutex as TokioMutex, Semaphore};
|
use tokio::sync::{broadcast, watch::Receiver, Mutex as TokioMutex, Semaphore};
|
||||||
use tracing::error;
|
use tracing::{error, info};
|
||||||
use trust_dns_resolver::TokioAsyncResolver;
|
use trust_dns_resolver::TokioAsyncResolver;
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
|
||||||
type WellKnownMap = HashMap<OwnedServerName, (FedDest, String)>;
|
type WellKnownMap = HashMap<OwnedServerName, (FedDest, String)>;
|
||||||
type TlsNameMap = HashMap<String, (Vec<IpAddr>, u16)>;
|
type TlsNameMap = HashMap<String, (Vec<IpAddr>, u16)>;
|
||||||
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
||||||
|
@ -50,6 +64,7 @@ pub struct Service {
|
||||||
pub unstable_room_versions: Vec<RoomVersionId>,
|
pub unstable_room_versions: Vec<RoomVersionId>,
|
||||||
pub bad_event_ratelimiter: Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
|
pub bad_event_ratelimiter: Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
|
||||||
pub bad_signature_ratelimiter: Arc<RwLock<HashMap<Vec<String>, RateLimitState>>>,
|
pub bad_signature_ratelimiter: Arc<RwLock<HashMap<Vec<String>, RateLimitState>>>,
|
||||||
|
pub bad_query_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, RateLimitState>>>,
|
||||||
pub servername_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, Arc<Semaphore>>>>,
|
pub servername_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, Arc<Semaphore>>>>,
|
||||||
pub sync_receivers: RwLock<HashMap<(OwnedUserId, OwnedDeviceId), SyncHandle>>,
|
pub sync_receivers: RwLock<HashMap<(OwnedUserId, OwnedDeviceId), SyncHandle>>,
|
||||||
pub roomid_mutex_insert: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
pub roomid_mutex_insert: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||||
|
@ -58,6 +73,8 @@ pub struct Service {
|
||||||
pub roomid_federationhandletime: RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
pub roomid_federationhandletime: RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
||||||
pub stateres_mutex: Arc<Mutex<()>>,
|
pub stateres_mutex: Arc<Mutex<()>>,
|
||||||
pub rotate: RotationHandler,
|
pub rotate: RotationHandler,
|
||||||
|
|
||||||
|
pub shutdown: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles "rotation" of long-polling requests. "Rotation" in this context is similar to "rotation" of log files and the like.
|
/// Handles "rotation" of long-polling requests. "Rotation" in this context is similar to "rotation" of log files and the like.
|
||||||
|
@ -90,6 +107,45 @@ impl Default for RotationHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Resolver {
|
||||||
|
inner: GaiResolver,
|
||||||
|
overrides: Arc<RwLock<TlsNameMap>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolver {
|
||||||
|
pub fn new(overrides: Arc<RwLock<TlsNameMap>>) -> Self {
|
||||||
|
Resolver {
|
||||||
|
inner: GaiResolver::new(),
|
||||||
|
overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for Resolver {
|
||||||
|
fn resolve(&self, name: Name) -> Resolving {
|
||||||
|
self.overrides
|
||||||
|
.read()
|
||||||
|
.expect("lock should not be poisoned")
|
||||||
|
.get(name.as_str())
|
||||||
|
.and_then(|(override_name, port)| {
|
||||||
|
override_name.first().map(|first_name| {
|
||||||
|
let x: Box<dyn Iterator<Item = SocketAddr> + Send> =
|
||||||
|
Box::new(iter::once(SocketAddr::new(*first_name, *port)));
|
||||||
|
let x: Resolving = Box::pin(future::ready(Ok(x)));
|
||||||
|
x
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let this = &mut self.inner.clone();
|
||||||
|
Box::pin(HyperService::<Name>::call(this, name).map(|result| {
|
||||||
|
result
|
||||||
|
.map(|addrs| -> Addrs { Box::new(addrs) })
|
||||||
|
.map_err(|err| -> Box<dyn StdError + Send + Sync> { Box::new(err) })
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn load(db: &'static dyn Data, config: Config) -> Result<Self> {
|
pub fn load(db: &'static dyn Data, config: Config) -> Result<Self> {
|
||||||
let keypair = db.load_keypair();
|
let keypair = db.load_keypair();
|
||||||
|
@ -111,14 +167,8 @@ impl Service {
|
||||||
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()));
|
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()));
|
||||||
|
|
||||||
let default_client = reqwest_client_builder(&config)?.build()?;
|
let default_client = reqwest_client_builder(&config)?.build()?;
|
||||||
let name_override = Arc::clone(&tls_name_override);
|
|
||||||
let federation_client = reqwest_client_builder(&config)?
|
let federation_client = reqwest_client_builder(&config)?
|
||||||
.resolve_fn(move |domain| {
|
.dns_resolver(Arc::new(Resolver::new(tls_name_override.clone())))
|
||||||
let read_guard = name_override.read().unwrap();
|
|
||||||
let (override_name, port) = read_guard.get(&domain)?;
|
|
||||||
let first_name = override_name.get(0)?;
|
|
||||||
Some(SocketAddr::new(*first_name, *port))
|
|
||||||
})
|
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
// Supported and stable room versions
|
// Supported and stable room versions
|
||||||
|
@ -152,6 +202,7 @@ impl Service {
|
||||||
unstable_room_versions,
|
unstable_room_versions,
|
||||||
bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||||
bad_signature_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
bad_signature_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
bad_query_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||||
servername_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
servername_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||||
roomid_mutex_state: RwLock::new(HashMap::new()),
|
roomid_mutex_state: RwLock::new(HashMap::new()),
|
||||||
roomid_mutex_insert: RwLock::new(HashMap::new()),
|
roomid_mutex_insert: RwLock::new(HashMap::new()),
|
||||||
|
@ -160,6 +211,7 @@ impl Service {
|
||||||
stateres_mutex: Arc::new(Mutex::new(())),
|
stateres_mutex: Arc::new(Mutex::new(())),
|
||||||
sync_receivers: RwLock::new(HashMap::new()),
|
sync_receivers: RwLock::new(HashMap::new()),
|
||||||
rotate: RotationHandler::new(),
|
rotate: RotationHandler::new(),
|
||||||
|
shutdown: AtomicBool::new(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::create_dir_all(s.get_media_folder())?;
|
fs::create_dir_all(s.get_media_folder())?;
|
||||||
|
@ -202,6 +254,16 @@ impl Service {
|
||||||
self.db.current_count()
|
self.db.current_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub fn last_check_for_updates_id(&self) -> Result<u64> {
|
||||||
|
self.db.last_check_for_updates_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub fn update_check_for_updates_id(&self, id: u64) -> Result<()> {
|
||||||
|
self.db.update_check_for_updates_id(id)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||||
self.db.watch(user_id, device_id).await
|
self.db.watch(user_id, device_id).await
|
||||||
}
|
}
|
||||||
|
@ -210,10 +272,6 @@ impl Service {
|
||||||
self.db.cleanup()
|
self.db.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn memory_usage(&self) -> Result<String> {
|
|
||||||
self.db.memory_usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn server_name(&self) -> &ServerName {
|
pub fn server_name(&self) -> &ServerName {
|
||||||
self.config.server_name.as_ref()
|
self.config.server_name.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -254,6 +312,10 @@ impl Service {
|
||||||
self.config.enable_lightning_bolt
|
self.config.enable_lightning_bolt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allow_check_for_updates(&self) -> bool {
|
||||||
|
self.config.allow_check_for_updates
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trusted_servers(&self) -> &[OwnedServerName] {
|
pub fn trusted_servers(&self) -> &[OwnedServerName] {
|
||||||
&self.config.trusted_servers
|
&self.config.trusted_servers
|
||||||
}
|
}
|
||||||
|
@ -316,7 +378,19 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
origin: &ServerName,
|
origin: &ServerName,
|
||||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
||||||
self.db.signing_keys_for(origin)
|
let mut keys = self.db.signing_keys_for(origin)?;
|
||||||
|
if origin == self.server_name() {
|
||||||
|
keys.insert(
|
||||||
|
format!("ed25519:{}", services().globals.keypair().version())
|
||||||
|
.try_into()
|
||||||
|
.expect("found invalid server signing keys in DB"),
|
||||||
|
VerifyKey {
|
||||||
|
key: Base64::new(self.keypair.public_key().to_vec()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn database_version(&self) -> Result<u64> {
|
pub fn database_version(&self) -> Result<u64> {
|
||||||
|
@ -338,9 +412,20 @@ impl Service {
|
||||||
let mut r = PathBuf::new();
|
let mut r = PathBuf::new();
|
||||||
r.push(self.config.database_path.clone());
|
r.push(self.config.database_path.clone());
|
||||||
r.push("media");
|
r.push("media");
|
||||||
r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
|
r.push(general_purpose::URL_SAFE_NO_PAD.encode(key));
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn well_known_client(&self) -> &Option<String> {
|
||||||
|
&self.config.well_known_client
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(&self) {
|
||||||
|
self.shutdown.store(true, atomic::Ordering::Relaxed);
|
||||||
|
// On shutdown
|
||||||
|
info!(target: "shutdown-sync", "Received shutdown notification, notifying sync helpers...");
|
||||||
|
services().globals.rotate.fire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reqwest_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
|
fn reqwest_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use image::imageops::FilterType;
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FileMeta {
|
pub struct FileMeta {
|
||||||
|
@ -70,7 +70,9 @@ impl Service {
|
||||||
{
|
{
|
||||||
let path = services().globals.get_media_file(&key);
|
let path = services().globals.get_media_file(&key);
|
||||||
let mut file = Vec::new();
|
let mut file = Vec::new();
|
||||||
File::open(path).await?.read_to_end(&mut file).await?;
|
BufReader::new(File::open(path).await?)
|
||||||
|
.read_to_end(&mut file)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Some(FileMeta {
|
Ok(Some(FileMeta {
|
||||||
content_disposition,
|
content_disposition,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{BTreeMap, HashMap},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,11 +97,18 @@ impl Services {
|
||||||
db,
|
db,
|
||||||
lasttimelinecount_cache: Mutex::new(HashMap::new()),
|
lasttimelinecount_cache: Mutex::new(HashMap::new()),
|
||||||
},
|
},
|
||||||
|
threads: rooms::threads::Service { db },
|
||||||
|
spaces: rooms::spaces::Service {
|
||||||
|
roomid_spacechunk_cache: Mutex::new(LruCache::new(200)),
|
||||||
|
},
|
||||||
user: rooms::user::Service { db },
|
user: rooms::user::Service { db },
|
||||||
},
|
},
|
||||||
transaction_ids: transaction_ids::Service { db },
|
transaction_ids: transaction_ids::Service { db },
|
||||||
uiaa: uiaa::Service { db },
|
uiaa: uiaa::Service { db },
|
||||||
users: users::Service { db },
|
users: users::Service {
|
||||||
|
db,
|
||||||
|
connections: Mutex::new(BTreeMap::new()),
|
||||||
|
},
|
||||||
account_data: account_data::Service { db },
|
account_data: account_data::Service { db },
|
||||||
admin: admin::Service::build(),
|
admin: admin::Service::build(),
|
||||||
key_backups: key_backups::Service { db },
|
key_backups: key_backups::Service { db },
|
||||||
|
@ -111,4 +118,109 @@ impl Services {
|
||||||
globals: globals::Service::load(db, config)?,
|
globals: globals::Service::load(db, config)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn memory_usage(&self) -> String {
|
||||||
|
let lazy_load_waiting = self
|
||||||
|
.rooms
|
||||||
|
.lazy_loading
|
||||||
|
.lazy_load_waiting
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
let server_visibility_cache = self
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.server_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
let user_visibility_cache = self
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
let stateinfo_cache = self
|
||||||
|
.rooms
|
||||||
|
.state_compressor
|
||||||
|
.stateinfo_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
let lasttimelinecount_cache = self
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.lasttimelinecount_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
let roomid_spacechunk_cache = self
|
||||||
|
.rooms
|
||||||
|
.spaces
|
||||||
|
.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"\
|
||||||
|
lazy_load_waiting: {lazy_load_waiting}
|
||||||
|
server_visibility_cache: {server_visibility_cache}
|
||||||
|
user_visibility_cache: {user_visibility_cache}
|
||||||
|
stateinfo_cache: {stateinfo_cache}
|
||||||
|
lasttimelinecount_cache: {lasttimelinecount_cache}
|
||||||
|
roomid_spacechunk_cache: {roomid_spacechunk_cache}\
|
||||||
|
"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn clear_caches(&self, amount: u32) {
|
||||||
|
if amount > 0 {
|
||||||
|
self.rooms
|
||||||
|
.lazy_loading
|
||||||
|
.lazy_load_waiting
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
if amount > 1 {
|
||||||
|
self.rooms
|
||||||
|
.state_accessor
|
||||||
|
.server_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
if amount > 2 {
|
||||||
|
self.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
if amount > 3 {
|
||||||
|
self.rooms
|
||||||
|
.state_compressor
|
||||||
|
.stateinfo_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
if amount > 4 {
|
||||||
|
self.rooms
|
||||||
|
.timeline
|
||||||
|
.lasttimelinecount_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
if amount > 5 {
|
||||||
|
self.rooms
|
||||||
|
.spaces
|
||||||
|
.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::member::RoomMemberEventContent, AnyEphemeralRoomEvent, AnyStateEvent,
|
room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent,
|
||||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent,
|
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
|
||||||
RoomEventType, StateEvent,
|
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, TimelineEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||||
|
@ -31,7 +31,7 @@ pub struct PduEvent {
|
||||||
pub sender: OwnedUserId,
|
pub sender: OwnedUserId,
|
||||||
pub origin_server_ts: UInt,
|
pub origin_server_ts: UInt,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub kind: RoomEventType,
|
pub kind: TimelineEventType,
|
||||||
pub content: Box<RawJsonValue>,
|
pub content: Box<RawJsonValue>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub state_key: Option<String>,
|
pub state_key: Option<String>,
|
||||||
|
@ -53,10 +53,10 @@ impl PduEvent {
|
||||||
self.unsigned = None;
|
self.unsigned = None;
|
||||||
|
|
||||||
let allowed: &[&str] = match self.kind {
|
let allowed: &[&str] = match self.kind {
|
||||||
RoomEventType::RoomMember => &["join_authorised_via_users_server", "membership"],
|
TimelineEventType::RoomMember => &["join_authorised_via_users_server", "membership"],
|
||||||
RoomEventType::RoomCreate => &["creator"],
|
TimelineEventType::RoomCreate => &["creator"],
|
||||||
RoomEventType::RoomJoinRules => &["join_rule"],
|
TimelineEventType::RoomJoinRules => &["join_rule"],
|
||||||
RoomEventType::RoomPowerLevels => &[
|
TimelineEventType::RoomPowerLevels => &[
|
||||||
"ban",
|
"ban",
|
||||||
"events",
|
"events",
|
||||||
"events_default",
|
"events_default",
|
||||||
|
@ -66,7 +66,7 @@ impl PduEvent {
|
||||||
"users",
|
"users",
|
||||||
"users_default",
|
"users_default",
|
||||||
],
|
],
|
||||||
RoomEventType::RoomHistoryVisibility => &["history_visibility"],
|
TimelineEventType::RoomHistoryVisibility => &["history_visibility"],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,6 +103,19 @@ impl PduEvent {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_age(&mut self) -> crate::Result<()> {
|
||||||
|
let mut unsigned: BTreeMap<String, Box<RawJsonValue>> = self
|
||||||
|
.unsigned
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| Ok(BTreeMap::new()), |u| serde_json::from_str(u.get()))
|
||||||
|
.map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?;
|
||||||
|
|
||||||
|
unsigned.insert("age".to_owned(), to_raw_value(&1).unwrap());
|
||||||
|
self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
|
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
|
||||||
let mut json = json!({
|
let mut json = json!({
|
||||||
|
@ -175,6 +188,30 @@ impl PduEvent {
|
||||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
|
||||||
|
let mut json = json!({
|
||||||
|
"content": self.content,
|
||||||
|
"type": self.kind,
|
||||||
|
"event_id": self.event_id,
|
||||||
|
"sender": self.sender,
|
||||||
|
"origin_server_ts": self.origin_server_ts,
|
||||||
|
"room_id": self.room_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(unsigned) = &self.unsigned {
|
||||||
|
json["unsigned"] = json!(unsigned);
|
||||||
|
}
|
||||||
|
if let Some(state_key) = &self.state_key {
|
||||||
|
json["state_key"] = json!(state_key);
|
||||||
|
}
|
||||||
|
if let Some(redacts) = &self.redacts {
|
||||||
|
json["redacts"] = json!(redacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
|
pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
|
||||||
let mut json = json!({
|
let mut json = json!({
|
||||||
|
@ -224,6 +261,19 @@ impl PduEvent {
|
||||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub fn to_stripped_spacechild_state_event(&self) -> Raw<HierarchySpaceChildEvent> {
|
||||||
|
let json = json!({
|
||||||
|
"content": self.content,
|
||||||
|
"type": self.kind,
|
||||||
|
"sender": self.sender,
|
||||||
|
"state_key": self.state_key,
|
||||||
|
"origin_server_ts": self.origin_server_ts,
|
||||||
|
});
|
||||||
|
|
||||||
|
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn to_member_event(&self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
pub fn to_member_event(&self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
||||||
let mut json = json!({
|
let mut json = json!({
|
||||||
|
@ -296,7 +346,7 @@ impl state_res::Event for PduEvent {
|
||||||
&self.sender
|
&self.sender
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_type(&self) -> &RoomEventType {
|
fn event_type(&self) -> &TimelineEventType {
|
||||||
&self.kind
|
&self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +385,7 @@ impl PartialEq for PduEvent {
|
||||||
}
|
}
|
||||||
impl PartialOrd for PduEvent {
|
impl PartialOrd for PduEvent {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.event_id.partial_cmp(&other.event_id)
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Ord for PduEvent {
|
impl Ord for PduEvent {
|
||||||
|
@ -372,7 +422,7 @@ pub(crate) fn gen_event_id_canonical_json(
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct PduBuilder {
|
pub struct PduBuilder {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub event_type: RoomEventType,
|
pub event_type: TimelineEventType,
|
||||||
pub content: Box<RawJsonValue>,
|
pub content: Box<RawJsonValue>,
|
||||||
pub unsigned: Option<BTreeMap<String, serde_json::Value>>,
|
pub unsigned: Option<BTreeMap<String, serde_json::Value>>,
|
||||||
pub state_key: Option<String>,
|
pub state_key: Option<String>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod data;
|
mod data;
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use ruma::events::AnySyncTimelineEvent;
|
use ruma::{events::AnySyncTimelineEvent, push::PushConditionPowerLevelsCtx};
|
||||||
|
|
||||||
use crate::{services, Error, PduEvent, Result};
|
use crate::{services, Error, PduEvent, Result};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
@ -13,10 +13,7 @@ use ruma::{
|
||||||
},
|
},
|
||||||
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||||
},
|
},
|
||||||
events::{
|
events::{room::power_levels::RoomPowerLevelsEventContent, StateEventType, TimelineEventType},
|
||||||
room::{name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent},
|
|
||||||
RoomEventType, StateEventType,
|
|
||||||
},
|
|
||||||
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
uint, RoomId, UInt, UserId,
|
uint, RoomId, UInt, UserId,
|
||||||
|
@ -162,13 +159,12 @@ impl Service {
|
||||||
&pdu.room_id,
|
&pdu.room_id,
|
||||||
)? {
|
)? {
|
||||||
let n = match action {
|
let n = match action {
|
||||||
Action::DontNotify => false,
|
Action::Notify => true,
|
||||||
// TODO: Implement proper support for coalesce
|
|
||||||
Action::Notify | Action::Coalesce => true,
|
|
||||||
Action::SetTweak(tweak) => {
|
Action::SetTweak(tweak) => {
|
||||||
tweaks.push(tweak.clone());
|
tweaks.push(tweak.clone());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if notify.is_some() {
|
if notify.is_some() {
|
||||||
|
@ -197,6 +193,12 @@ impl Service {
|
||||||
pdu: &Raw<AnySyncTimelineEvent>,
|
pdu: &Raw<AnySyncTimelineEvent>,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
) -> Result<&'a [Action]> {
|
) -> Result<&'a [Action]> {
|
||||||
|
let power_levels = PushConditionPowerLevelsCtx {
|
||||||
|
users: power_levels.users.clone(),
|
||||||
|
users_default: power_levels.users_default,
|
||||||
|
notifications: power_levels.notifications.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let ctx = PushConditionRoomCtx {
|
let ctx = PushConditionRoomCtx {
|
||||||
room_id: room_id.to_owned(),
|
room_id: room_id.to_owned(),
|
||||||
member_count: 10_u32.into(), // TODO: get member count efficiently
|
member_count: 10_u32.into(), // TODO: get member count efficiently
|
||||||
|
@ -205,9 +207,7 @@ impl Service {
|
||||||
.users
|
.users
|
||||||
.displayname(user)?
|
.displayname(user)?
|
||||||
.unwrap_or_else(|| user.localpart().to_owned()),
|
.unwrap_or_else(|| user.localpart().to_owned()),
|
||||||
users_power_levels: power_levels.users.clone(),
|
power_levels: Some(power_levels),
|
||||||
default_power_level: power_levels.users_default,
|
|
||||||
notification_power_levels: power_levels.notifications.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ruleset.get_actions(pdu, &ctx))
|
Ok(ruleset.get_actions(pdu, &ctx))
|
||||||
|
@ -248,7 +248,7 @@ impl Service {
|
||||||
// TODO: missed calls
|
// TODO: missed calls
|
||||||
notifi.counts = NotificationCounts::new(unread, uint!(0));
|
notifi.counts = NotificationCounts::new(unread, uint!(0));
|
||||||
|
|
||||||
if event.kind == RoomEventType::RoomEncrypted
|
if event.kind == TimelineEventType::RoomEncrypted
|
||||||
|| tweaks
|
|| tweaks
|
||||||
.iter()
|
.iter()
|
||||||
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
|
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
|
||||||
|
@ -264,28 +264,14 @@ impl Service {
|
||||||
notifi.event_type = Some(event.kind.clone());
|
notifi.event_type = Some(event.kind.clone());
|
||||||
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
|
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
|
||||||
|
|
||||||
if event.kind == RoomEventType::RoomMember {
|
if event.kind == TimelineEventType::RoomMember {
|
||||||
notifi.user_is_target =
|
notifi.user_is_target =
|
||||||
event.state_key.as_deref() == Some(event.sender.as_str());
|
event.state_key.as_deref() == Some(event.sender.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
notifi.sender_display_name = services().users.displayname(&event.sender)?;
|
notifi.sender_display_name = services().users.displayname(&event.sender)?;
|
||||||
|
|
||||||
let room_name = if let Some(room_name_pdu) = services()
|
notifi.room_name = services().rooms.state_accessor.get_name(&event.room_id)?;
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&event.room_id, &StateEventType::RoomName, "")?
|
|
||||||
{
|
|
||||||
serde_json::from_str::<RoomNameEventContent>(room_name_pdu.content.get())
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database("Invalid room name event in database.")
|
|
||||||
})?
|
|
||||||
.name
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
notifi.room_name = room_name;
|
|
||||||
|
|
||||||
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub trait Data: Send + Sync {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn readreceipts_since<'a>(
|
fn readreceipts_since<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
|
|
|
@ -38,6 +38,8 @@ use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use crate::{service::*, services, Error, PduEvent, Result};
|
use crate::{service::*, services, Error, PduEvent, Result};
|
||||||
|
|
||||||
|
use super::state_compressor::CompressedStateEvent;
|
||||||
|
|
||||||
pub struct Service;
|
pub struct Service;
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
@ -62,9 +64,8 @@ impl Service {
|
||||||
/// 12. Ensure that the state is derived from the previous current state (i.e. we calculated by
|
/// 12. Ensure that the state is derived from the previous current state (i.e. we calculated by
|
||||||
/// doing state res where one of the inputs was a previously trusted set of state, don't just
|
/// doing state res where one of the inputs was a previously trusted set of state, don't just
|
||||||
/// trust a set of state we got from a remote)
|
/// trust a set of state we got from a remote)
|
||||||
/// 13. Check if the event passes auth based on the "current state" of the room, if not "soft fail"
|
/// 13. Use state resolution to find new room state
|
||||||
/// it
|
/// 14. Check if the event passes auth based on the "current state" of the room, if not soft fail it
|
||||||
/// 14. Use state resolution to find new room state
|
|
||||||
// We use some AsyncRecursiveType hacks here so we can call this async funtion recursively
|
// We use some AsyncRecursiveType hacks here so we can call this async funtion recursively
|
||||||
#[tracing::instrument(skip(self, value, is_timeline_event, pub_key_map))]
|
#[tracing::instrument(skip(self, value, is_timeline_event, pub_key_map))]
|
||||||
pub(crate) async fn handle_incoming_pdu<'a>(
|
pub(crate) async fn handle_incoming_pdu<'a>(
|
||||||
|
@ -91,6 +92,8 @@ impl Service {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
services().rooms.event_handler.acl_check(origin, room_id)?;
|
||||||
|
|
||||||
// 1. Skip the PDU if we already have it as a timeline event
|
// 1. Skip the PDU if we already have it as a timeline event
|
||||||
if let Some(pdu_id) = services().rooms.timeline.get_pdu_id(event_id)? {
|
if let Some(pdu_id) = services().rooms.timeline.get_pdu_id(event_id)? {
|
||||||
return Ok(Some(pdu_id.to_vec()));
|
return Ok(Some(pdu_id.to_vec()));
|
||||||
|
@ -116,8 +119,17 @@ impl Service {
|
||||||
.ok_or_else(|| Error::bad_database("Failed to find first pdu in db."))?;
|
.ok_or_else(|| Error::bad_database("Failed to find first pdu in db."))?;
|
||||||
|
|
||||||
let (incoming_pdu, val) = self
|
let (incoming_pdu, val) = self
|
||||||
.handle_outlier_pdu(origin, &create_event, event_id, room_id, value, pub_key_map)
|
.handle_outlier_pdu(
|
||||||
|
origin,
|
||||||
|
&create_event,
|
||||||
|
event_id,
|
||||||
|
room_id,
|
||||||
|
value,
|
||||||
|
false,
|
||||||
|
pub_key_map,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
self.check_room_id(room_id, &incoming_pdu)?;
|
||||||
|
|
||||||
// 8. if not timeline event: stop
|
// 8. if not timeline event: stop
|
||||||
if !is_timeline_event {
|
if !is_timeline_event {
|
||||||
|
@ -172,7 +184,22 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors >= 5 {
|
if errors >= 5 {
|
||||||
break;
|
// Timeout other events
|
||||||
|
match services()
|
||||||
|
.globals
|
||||||
|
.bad_event_ratelimiter
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry((*prev_id).to_owned())
|
||||||
|
{
|
||||||
|
hash_map::Entry::Vacant(e) => {
|
||||||
|
e.insert((Instant::now(), 1));
|
||||||
|
}
|
||||||
|
hash_map::Entry::Occupied(mut e) => {
|
||||||
|
*e.get_mut() = (Instant::now(), e.get().1 + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((pdu, json)) = eventid_info.remove(&*prev_id) {
|
if let Some((pdu, json)) = eventid_info.remove(&*prev_id) {
|
||||||
|
@ -224,7 +251,7 @@ impl Service {
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.remove(&room_id.to_owned());
|
.remove(&room_id.to_owned());
|
||||||
warn!(
|
debug!(
|
||||||
"Handling prev event {} took {}m{}s",
|
"Handling prev event {} took {}m{}s",
|
||||||
prev_id,
|
prev_id,
|
||||||
elapsed.as_secs() / 60,
|
elapsed.as_secs() / 60,
|
||||||
|
@ -264,6 +291,7 @@ impl Service {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
|
||||||
#[tracing::instrument(skip(self, create_event, value, pub_key_map))]
|
#[tracing::instrument(skip(self, create_event, value, pub_key_map))]
|
||||||
fn handle_outlier_pdu<'a>(
|
fn handle_outlier_pdu<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -272,6 +300,7 @@ impl Service {
|
||||||
event_id: &'a EventId,
|
event_id: &'a EventId,
|
||||||
room_id: &'a RoomId,
|
room_id: &'a RoomId,
|
||||||
mut value: BTreeMap<String, CanonicalJsonValue>,
|
mut value: BTreeMap<String, CanonicalJsonValue>,
|
||||||
|
auth_events_known: bool,
|
||||||
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
||||||
) -> AsyncRecursiveType<'a, Result<(Arc<PduEvent>, BTreeMap<String, CanonicalJsonValue>)>> {
|
) -> AsyncRecursiveType<'a, Result<(Arc<PduEvent>, BTreeMap<String, CanonicalJsonValue>)>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
@ -304,7 +333,7 @@ impl Service {
|
||||||
) {
|
) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Drop
|
// Drop
|
||||||
warn!("Dropping bad event {}: {}", event_id, e);
|
warn!("Dropping bad event {}: {}", event_id, e,);
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
"Signature verification failed",
|
"Signature verification failed",
|
||||||
|
@ -313,7 +342,7 @@ impl Service {
|
||||||
Ok(ruma::signatures::Verified::Signatures) => {
|
Ok(ruma::signatures::Verified::Signatures) => {
|
||||||
// Redact
|
// Redact
|
||||||
warn!("Calculated hash does not match: {}", event_id);
|
warn!("Calculated hash does not match: {}", event_id);
|
||||||
match ruma::canonical_json::redact(value, room_version_id, None) {
|
let obj = match ruma::canonical_json::redact(value, room_version_id, None) {
|
||||||
Ok(obj) => obj,
|
Ok(obj) => obj,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
|
@ -321,7 +350,17 @@ impl Service {
|
||||||
"Redaction failed",
|
"Redaction failed",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip the PDU if it is redacted and we already have it as an outlier event
|
||||||
|
if services().rooms.timeline.get_pdu_json(event_id)?.is_some() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Event was redacted and we already knew about it",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj
|
||||||
}
|
}
|
||||||
Ok(ruma::signatures::Verified::All) => value,
|
Ok(ruma::signatures::Verified::All) => value,
|
||||||
};
|
};
|
||||||
|
@ -337,6 +376,9 @@ impl Service {
|
||||||
)
|
)
|
||||||
.map_err(|_| Error::bad_database("Event is not a valid PDU."))?;
|
.map_err(|_| Error::bad_database("Event is not a valid PDU."))?;
|
||||||
|
|
||||||
|
self.check_room_id(room_id, &incoming_pdu)?;
|
||||||
|
|
||||||
|
if !auth_events_known {
|
||||||
// 4. fetch any missing auth events doing all checks listed here starting at 1. These are not timeline events
|
// 4. fetch any missing auth events doing all checks listed here starting at 1. These are not timeline events
|
||||||
// 5. Reject "due to auth events" if can't get all the auth events or some of the auth events are also rejected "due to auth events"
|
// 5. Reject "due to auth events" if can't get all the auth events or some of the auth events are also rejected "due to auth events"
|
||||||
// NOTE: Step 5 is not applied anymore because it failed too often
|
// NOTE: Step 5 is not applied anymore because it failed too often
|
||||||
|
@ -354,9 +396,10 @@ impl Service {
|
||||||
pub_key_map,
|
pub_key_map,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Reject "due to auth events" if the event doesn't pass auth based on the auth events
|
// 6. Reject "due to auth events" if the event doesn't pass auth based on the auth events
|
||||||
info!(
|
debug!(
|
||||||
"Auth check for {} based on auth events",
|
"Auth check for {} based on auth events",
|
||||||
incoming_pdu.event_id
|
incoming_pdu.event_id
|
||||||
);
|
);
|
||||||
|
@ -372,6 +415,8 @@ impl Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.check_room_id(room_id, &auth_event)?;
|
||||||
|
|
||||||
match auth_events.entry((
|
match auth_events.entry((
|
||||||
auth_event.kind.to_string().into(),
|
auth_event.kind.to_string().into(),
|
||||||
auth_event
|
auth_event
|
||||||
|
@ -418,7 +463,7 @@ impl Service {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Validation successful.");
|
debug!("Validation successful.");
|
||||||
|
|
||||||
// 7. Persist the event as an outlier.
|
// 7. Persist the event as an outlier.
|
||||||
services()
|
services()
|
||||||
|
@ -426,7 +471,7 @@ impl Service {
|
||||||
.outlier
|
.outlier
|
||||||
.add_pdu_outlier(&incoming_pdu.event_id, &val)?;
|
.add_pdu_outlier(&incoming_pdu.event_id, &val)?;
|
||||||
|
|
||||||
info!("Added pdu as outlier.");
|
debug!("Added pdu as outlier.");
|
||||||
|
|
||||||
Ok((Arc::new(incoming_pdu), val))
|
Ok((Arc::new(incoming_pdu), val))
|
||||||
})
|
})
|
||||||
|
@ -475,7 +520,7 @@ impl Service {
|
||||||
// TODO: if we know the prev_events of the incoming event we can avoid the request and build
|
// TODO: if we know the prev_events of the incoming event we can avoid the request and build
|
||||||
// the state from a known point and resolve if > 1 prev_event
|
// the state from a known point and resolve if > 1 prev_event
|
||||||
|
|
||||||
info!("Requesting state at event");
|
debug!("Requesting state at event");
|
||||||
let mut state_at_incoming_event = None;
|
let mut state_at_incoming_event = None;
|
||||||
|
|
||||||
if incoming_pdu.prev_events.len() == 1 {
|
if incoming_pdu.prev_events.len() == 1 {
|
||||||
|
@ -498,7 +543,7 @@ impl Service {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(Ok(mut state)) = state {
|
if let Some(Ok(mut state)) = state {
|
||||||
info!("Using cached state");
|
debug!("Using cached state");
|
||||||
let prev_pdu = services()
|
let prev_pdu = services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
|
@ -522,7 +567,7 @@ impl Service {
|
||||||
state_at_incoming_event = Some(state);
|
state_at_incoming_event = Some(state);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Calculating state at event using state res");
|
debug!("Calculating state at event using state res");
|
||||||
let mut extremity_sstatehashes = HashMap::new();
|
let mut extremity_sstatehashes = HashMap::new();
|
||||||
|
|
||||||
let mut okay = true;
|
let mut okay = true;
|
||||||
|
@ -631,7 +676,7 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state_at_incoming_event.is_none() {
|
if state_at_incoming_event.is_none() {
|
||||||
info!("Calling /state_ids");
|
debug!("Calling /state_ids");
|
||||||
// Call /state_ids to find out what the state at this pdu is. We trust the server's
|
// Call /state_ids to find out what the state at this pdu is. We trust the server's
|
||||||
// response to some extend, but we still do a lot of checks on the events
|
// response to some extend, but we still do a lot of checks on the events
|
||||||
match services()
|
match services()
|
||||||
|
@ -646,7 +691,7 @@ impl Service {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
info!("Fetching state events at event.");
|
debug!("Fetching state events at event.");
|
||||||
let state_vec = self
|
let state_vec = self
|
||||||
.fetch_and_handle_outliers(
|
.fetch_and_handle_outliers(
|
||||||
origin,
|
origin,
|
||||||
|
@ -709,7 +754,7 @@ impl Service {
|
||||||
let state_at_incoming_event =
|
let state_at_incoming_event =
|
||||||
state_at_incoming_event.expect("we always set this to some above");
|
state_at_incoming_event.expect("we always set this to some above");
|
||||||
|
|
||||||
info!("Starting auth check");
|
debug!("Starting auth check");
|
||||||
// 11. Check the auth of the event passes based on the state of the event
|
// 11. Check the auth of the event passes based on the state of the event
|
||||||
let check_result = state_res::event_auth::auth_check(
|
let check_result = state_res::event_auth::auth_check(
|
||||||
&room_version,
|
&room_version,
|
||||||
|
@ -733,10 +778,28 @@ impl Service {
|
||||||
"Event has failed auth check with state at the event.",
|
"Event has failed auth check with state at the event.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
info!("Auth check succeeded");
|
debug!("Auth check succeeded");
|
||||||
|
|
||||||
|
// Soft fail check before doing state res
|
||||||
|
let auth_events = services().rooms.state.get_auth_events(
|
||||||
|
room_id,
|
||||||
|
&incoming_pdu.kind,
|
||||||
|
&incoming_pdu.sender,
|
||||||
|
incoming_pdu.state_key.as_deref(),
|
||||||
|
&incoming_pdu.content,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let soft_fail = !state_res::event_auth::auth_check(
|
||||||
|
&room_version,
|
||||||
|
&incoming_pdu,
|
||||||
|
None::<PduEvent>,
|
||||||
|
|k, s| auth_events.get(&(k.clone(), s.to_owned())),
|
||||||
|
)
|
||||||
|
.map_err(|_e| Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed."))?;
|
||||||
|
|
||||||
|
// 13. Use state resolution to find new room state
|
||||||
|
|
||||||
// We start looking at current room state now, so lets lock the room
|
// We start looking at current room state now, so lets lock the room
|
||||||
|
|
||||||
let mutex_state = Arc::clone(
|
let mutex_state = Arc::clone(
|
||||||
services()
|
services()
|
||||||
.globals
|
.globals
|
||||||
|
@ -750,7 +813,7 @@ impl Service {
|
||||||
|
|
||||||
// Now we calculate the set of extremities this room has after the incoming event has been
|
// Now we calculate the set of extremities this room has after the incoming event has been
|
||||||
// applied. We start with the previous extremities (aka leaves)
|
// applied. We start with the previous extremities (aka leaves)
|
||||||
info!("Calculating extremities");
|
debug!("Calculating extremities");
|
||||||
let mut extremities = services().rooms.state.get_forward_extremities(room_id)?;
|
let mut extremities = services().rooms.state.get_forward_extremities(room_id)?;
|
||||||
|
|
||||||
// Remove any forward extremities that are referenced by this incoming event's prev_events
|
// Remove any forward extremities that are referenced by this incoming event's prev_events
|
||||||
|
@ -771,8 +834,9 @@ impl Service {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Compressing state at event");
|
debug!("Compressing state at event");
|
||||||
let state_ids_compressed = state_at_incoming_event
|
let state_ids_compressed = Arc::new(
|
||||||
|
state_at_incoming_event
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(shortstatekey, id)| {
|
.map(|(shortstatekey, id)| {
|
||||||
services()
|
services()
|
||||||
|
@ -780,26 +844,44 @@ impl Service {
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.compress_state_event(*shortstatekey, id)
|
.compress_state_event(*shortstatekey, id)
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?,
|
||||||
|
);
|
||||||
|
|
||||||
// 13. Check if the event passes auth based on the "current state" of the room, if not "soft fail" it
|
if incoming_pdu.state_key.is_some() {
|
||||||
info!("Starting soft fail auth check");
|
debug!("Preparing for stateres to derive new room state");
|
||||||
|
|
||||||
let auth_events = services().rooms.state.get_auth_events(
|
// We also add state after incoming event to the fork states
|
||||||
room_id,
|
let mut state_after = state_at_incoming_event.clone();
|
||||||
&incoming_pdu.kind,
|
if let Some(state_key) = &incoming_pdu.state_key {
|
||||||
&incoming_pdu.sender,
|
let shortstatekey = services().rooms.short.get_or_create_shortstatekey(
|
||||||
incoming_pdu.state_key.as_deref(),
|
&incoming_pdu.kind.to_string().into(),
|
||||||
&incoming_pdu.content,
|
state_key,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let soft_fail = !state_res::event_auth::auth_check(
|
state_after.insert(shortstatekey, Arc::from(&*incoming_pdu.event_id));
|
||||||
&room_version,
|
}
|
||||||
&incoming_pdu,
|
|
||||||
None::<PduEvent>,
|
let new_room_state = self
|
||||||
|k, s| auth_events.get(&(k.clone(), s.to_owned())),
|
.resolve_state(room_id, room_version_id, state_after)
|
||||||
)
|
.await?;
|
||||||
.map_err(|_e| Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed."))?;
|
|
||||||
|
// Set the new room state to the resolved state
|
||||||
|
debug!("Forcing new room state");
|
||||||
|
|
||||||
|
let (sstatehash, new, removed) = services()
|
||||||
|
.rooms
|
||||||
|
.state_compressor
|
||||||
|
.save_state(room_id, new_room_state)?;
|
||||||
|
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state
|
||||||
|
.force_state(room_id, sstatehash, new, removed, &state_lock)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14. Check if the event passes auth based on the "current state" of the room, if not soft fail it
|
||||||
|
debug!("Starting soft fail auth check");
|
||||||
|
|
||||||
if soft_fail {
|
if soft_fail {
|
||||||
services().rooms.timeline.append_incoming_pdu(
|
services().rooms.timeline.append_incoming_pdu(
|
||||||
|
@ -823,8 +905,36 @@ impl Service {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if incoming_pdu.state_key.is_some() {
|
debug!("Appending pdu to timeline");
|
||||||
info!("Loading current room state ids");
|
extremities.insert(incoming_pdu.event_id.clone());
|
||||||
|
|
||||||
|
// Now that the event has passed all auth it is added into the timeline.
|
||||||
|
// We use the `state_at_event` instead of `state_after` so we accurately
|
||||||
|
// represent the state for this event.
|
||||||
|
|
||||||
|
let pdu_id = services().rooms.timeline.append_incoming_pdu(
|
||||||
|
&incoming_pdu,
|
||||||
|
val,
|
||||||
|
extremities.iter().map(|e| (**e).to_owned()).collect(),
|
||||||
|
state_ids_compressed,
|
||||||
|
soft_fail,
|
||||||
|
&state_lock,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
debug!("Appended incoming pdu");
|
||||||
|
|
||||||
|
// Event has passed all auth/stateres checks
|
||||||
|
drop(state_lock);
|
||||||
|
Ok(pdu_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve_state(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
room_version_id: &RoomVersionId,
|
||||||
|
incoming_state: HashMap<u64, Arc<EventId>>,
|
||||||
|
) -> Result<Arc<HashSet<CompressedStateEvent>>> {
|
||||||
|
debug!("Loading current room state ids");
|
||||||
let current_sstatehash = services()
|
let current_sstatehash = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state
|
.state
|
||||||
|
@ -837,78 +947,7 @@ impl Service {
|
||||||
.state_full_ids(current_sstatehash)
|
.state_full_ids(current_sstatehash)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Preparing for stateres to derive new room state");
|
let fork_states = [current_state_ids, incoming_state];
|
||||||
let mut extremity_sstatehashes = HashMap::new();
|
|
||||||
|
|
||||||
info!(?extremities, "Loading extremities");
|
|
||||||
for id in &extremities {
|
|
||||||
match services().rooms.timeline.get_pdu(id)? {
|
|
||||||
Some(leaf_pdu) => {
|
|
||||||
extremity_sstatehashes.insert(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.pdu_shortstatehash(&leaf_pdu.event_id)?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
error!(
|
|
||||||
"Found extremity pdu with no statehash in db: {:?}",
|
|
||||||
leaf_pdu
|
|
||||||
);
|
|
||||||
Error::bad_database("Found pdu with no statehash in db.")
|
|
||||||
})?,
|
|
||||||
leaf_pdu,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("Missing state snapshot for {:?}", id);
|
|
||||||
return Err(Error::BadDatabase("Missing state snapshot."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut fork_states = Vec::new();
|
|
||||||
|
|
||||||
// 12. Ensure that the state is derived from the previous current state (i.e. we calculated
|
|
||||||
// by doing state res where one of the inputs was a previously trusted set of state,
|
|
||||||
// don't just trust a set of state we got from a remote).
|
|
||||||
|
|
||||||
// We do this by adding the current state to the list of fork states
|
|
||||||
extremity_sstatehashes.remove(¤t_sstatehash);
|
|
||||||
fork_states.push(current_state_ids);
|
|
||||||
|
|
||||||
// We also add state after incoming event to the fork states
|
|
||||||
let mut state_after = state_at_incoming_event.clone();
|
|
||||||
if let Some(state_key) = &incoming_pdu.state_key {
|
|
||||||
let shortstatekey = services().rooms.short.get_or_create_shortstatekey(
|
|
||||||
&incoming_pdu.kind.to_string().into(),
|
|
||||||
state_key,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
state_after.insert(shortstatekey, Arc::from(&*incoming_pdu.event_id));
|
|
||||||
}
|
|
||||||
fork_states.push(state_after);
|
|
||||||
|
|
||||||
let mut update_state = false;
|
|
||||||
// 14. Use state resolution to find new room state
|
|
||||||
let new_room_state = if fork_states.is_empty() {
|
|
||||||
panic!("State is empty");
|
|
||||||
} else if fork_states.iter().skip(1).all(|f| &fork_states[0] == f) {
|
|
||||||
info!("State resolution trivial");
|
|
||||||
// There was only one state, so it has to be the room's current state (because that is
|
|
||||||
// always included)
|
|
||||||
fork_states[0]
|
|
||||||
.iter()
|
|
||||||
.map(|(k, id)| {
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_compressor
|
|
||||||
.compress_state_event(*k, id)
|
|
||||||
})
|
|
||||||
.collect::<Result<_>>()?
|
|
||||||
} else {
|
|
||||||
info!("Loading auth chains");
|
|
||||||
// We do need to force an update to this room's state
|
|
||||||
update_state = true;
|
|
||||||
|
|
||||||
let mut auth_chain_sets = Vec::new();
|
let mut auth_chain_sets = Vec::new();
|
||||||
for state in &fork_states {
|
for state in &fork_states {
|
||||||
|
@ -916,16 +955,13 @@ impl Service {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.auth_chain
|
.auth_chain
|
||||||
.get_auth_chain(
|
.get_auth_chain(room_id, state.iter().map(|(_, id)| id.clone()).collect())
|
||||||
room_id,
|
|
||||||
state.iter().map(|(_, id)| id.clone()).collect(),
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Loading fork states");
|
debug!("Loading fork states");
|
||||||
|
|
||||||
let fork_states: Vec<_> = fork_states
|
let fork_states: Vec<_> = fork_states
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -943,20 +979,22 @@ impl Service {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
info!("Resolving state");
|
debug!("Resolving state");
|
||||||
|
|
||||||
|
let fetch_event = |id: &_| {
|
||||||
|
let res = services().rooms.timeline.get_pdu(id);
|
||||||
|
if let Err(e) = &res {
|
||||||
|
error!("LOOK AT ME Failed to fetch event: {}", e);
|
||||||
|
}
|
||||||
|
res.ok().flatten()
|
||||||
|
};
|
||||||
|
|
||||||
let lock = services().globals.stateres_mutex.lock();
|
let lock = services().globals.stateres_mutex.lock();
|
||||||
let state = match state_res::resolve(
|
let state = match state_res::resolve(
|
||||||
room_version_id,
|
room_version_id,
|
||||||
&fork_states,
|
&fork_states,
|
||||||
auth_chain_sets,
|
auth_chain_sets,
|
||||||
|id| {
|
fetch_event,
|
||||||
let res = services().rooms.timeline.get_pdu(id);
|
|
||||||
if let Err(e) = &res {
|
|
||||||
error!("LOOK AT ME Failed to fetch event: {}", e);
|
|
||||||
}
|
|
||||||
res.ok().flatten()
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
Ok(new_state) => new_state,
|
Ok(new_state) => new_state,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -966,59 +1004,23 @@ impl Service {
|
||||||
|
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
info!("State resolution done. Compressing state");
|
debug!("State resolution done. Compressing state");
|
||||||
|
|
||||||
state
|
let new_room_state = state
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|((event_type, state_key), event_id)| {
|
.map(|((event_type, state_key), event_id)| {
|
||||||
let shortstatekey = services().rooms.short.get_or_create_shortstatekey(
|
let shortstatekey = services()
|
||||||
&event_type.to_string().into(),
|
.rooms
|
||||||
&state_key,
|
.short
|
||||||
)?;
|
.get_or_create_shortstatekey(&event_type.to_string().into(), &state_key)?;
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.compress_state_event(shortstatekey, &event_id)
|
.compress_state_event(shortstatekey, &event_id)
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?
|
.collect::<Result<_>>()?;
|
||||||
};
|
|
||||||
|
|
||||||
// Set the new room state to the resolved state
|
Ok(Arc::new(new_room_state))
|
||||||
if update_state {
|
|
||||||
info!("Forcing new room state");
|
|
||||||
let (sstatehash, new, removed) = services()
|
|
||||||
.rooms
|
|
||||||
.state_compressor
|
|
||||||
.save_state(room_id, new_room_state)?;
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state
|
|
||||||
.force_state(room_id, sstatehash, new, removed, &state_lock)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Appending pdu to timeline");
|
|
||||||
extremities.insert(incoming_pdu.event_id.clone());
|
|
||||||
|
|
||||||
// Now that the event has passed all auth it is added into the timeline.
|
|
||||||
// We use the `state_at_event` instead of `state_after` so we accurately
|
|
||||||
// represent the state for this event.
|
|
||||||
|
|
||||||
let pdu_id = services().rooms.timeline.append_incoming_pdu(
|
|
||||||
&incoming_pdu,
|
|
||||||
val,
|
|
||||||
extremities.iter().map(|e| (**e).to_owned()).collect(),
|
|
||||||
state_ids_compressed,
|
|
||||||
soft_fail,
|
|
||||||
&state_lock,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
info!("Appended incoming pdu");
|
|
||||||
|
|
||||||
// Event has passed all auth/stateres checks
|
|
||||||
drop(state_lock);
|
|
||||||
Ok(pdu_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the event and auth it. Once the event is validated (steps 1 - 8)
|
/// Find the event and auth it. Once the event is validated (steps 1 - 8)
|
||||||
|
@ -1030,6 +1032,7 @@ impl Service {
|
||||||
/// b. Look at outlier pdu tree
|
/// b. Look at outlier pdu tree
|
||||||
/// c. Ask origin server over federation
|
/// c. Ask origin server over federation
|
||||||
/// d. TODO: Ask other servers over federation?
|
/// d. TODO: Ask other servers over federation?
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) fn fetch_and_handle_outliers<'a>(
|
pub(crate) fn fetch_and_handle_outliers<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -1057,26 +1060,6 @@ impl Service {
|
||||||
|
|
||||||
let mut pdus = vec![];
|
let mut pdus = vec![];
|
||||||
for id in events {
|
for id in events {
|
||||||
if let Some((time, tries)) = services()
|
|
||||||
.globals
|
|
||||||
.bad_event_ratelimiter
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.get(&**id)
|
|
||||||
{
|
|
||||||
// Exponential backoff
|
|
||||||
let mut min_elapsed_duration =
|
|
||||||
Duration::from_secs(5 * 60) * (*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 {
|
|
||||||
info!("Backing off from {}", id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a. Look in the main timeline (pduid_pdu tree)
|
// a. Look in the main timeline (pduid_pdu tree)
|
||||||
// b. Look at outlier pdu tree
|
// b. Look at outlier pdu tree
|
||||||
// (get_pdu_json checks both)
|
// (get_pdu_json checks both)
|
||||||
|
@ -1094,6 +1077,26 @@ impl Service {
|
||||||
let mut events_all = HashSet::new();
|
let mut events_all = HashSet::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(next_id) = todo_auth_events.pop() {
|
while let Some(next_id) = todo_auth_events.pop() {
|
||||||
|
if let Some((time, tries)) = services()
|
||||||
|
.globals
|
||||||
|
.bad_event_ratelimiter
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&*next_id)
|
||||||
|
{
|
||||||
|
// Exponential backoff
|
||||||
|
let mut min_elapsed_duration =
|
||||||
|
Duration::from_secs(5 * 60) * (*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 {
|
||||||
|
info!("Backing off from {}", next_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if events_all.contains(&next_id) {
|
if events_all.contains(&next_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1104,7 +1107,7 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(Some(_)) = services().rooms.timeline.get_pdu(&next_id) {
|
if let Ok(Some(_)) = services().rooms.timeline.get_pdu(&next_id) {
|
||||||
trace!("Found {} in db", id);
|
trace!("Found {} in db", next_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,6 +1166,26 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (next_id, value) in events_in_reverse_order.iter().rev() {
|
for (next_id, value) in events_in_reverse_order.iter().rev() {
|
||||||
|
if let Some((time, tries)) = services()
|
||||||
|
.globals
|
||||||
|
.bad_event_ratelimiter
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&**next_id)
|
||||||
|
{
|
||||||
|
// Exponential backoff
|
||||||
|
let mut min_elapsed_duration =
|
||||||
|
Duration::from_secs(5 * 60) * (*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 {
|
||||||
|
info!("Backing off from {}", next_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.handle_outlier_pdu(
|
.handle_outlier_pdu(
|
||||||
origin,
|
origin,
|
||||||
|
@ -1170,6 +1193,7 @@ impl Service {
|
||||||
next_id,
|
next_id,
|
||||||
room_id,
|
room_id,
|
||||||
value.clone(),
|
value.clone(),
|
||||||
|
true,
|
||||||
pub_key_map,
|
pub_key_map,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1227,6 +1251,8 @@ impl Service {
|
||||||
.await
|
.await
|
||||||
.pop()
|
.pop()
|
||||||
{
|
{
|
||||||
|
self.check_room_id(room_id, &pdu)?;
|
||||||
|
|
||||||
if amount > services().globals.max_fetch_prev_events() {
|
if amount > services().globals.max_fetch_prev_events() {
|
||||||
// Max limit reached
|
// Max limit reached
|
||||||
warn!("Max prev event limit reached!");
|
warn!("Max prev event limit reached!");
|
||||||
|
@ -1572,12 +1598,21 @@ impl Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if acl_event_content.allow.is_empty() {
|
||||||
|
// Ignore broken acl events
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if acl_event_content.is_allowed(server_name) {
|
if acl_event_content.is_allowed(server_name) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
info!(
|
||||||
|
"Server {} was denied by room ACL in {}",
|
||||||
|
server_name, room_id
|
||||||
|
);
|
||||||
Err(Error::BadRequest(
|
Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
"Server was denied by ACL",
|
"Server was denied by room ACL",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1747,4 +1782,15 @@ impl Service {
|
||||||
"Failed to find public key for server",
|
"Failed to find public key for server",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_room_id(&self, room_id: &RoomId, pdu: &PduEvent) -> Result<()> {
|
||||||
|
if pdu.room_id != room_id {
|
||||||
|
warn!("Found event from room {} in room {}", pdu.room_id, room_id);
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Event has wrong room id",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use super::timeline::PduCount;
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub lazy_load_waiting:
|
pub lazy_load_waiting:
|
||||||
Mutex<HashMap<(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount), HashSet<OwnedUserId>>>,
|
Mutex<HashMap<(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount), HashSet<OwnedUserId>>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,12 @@ pub mod outlier;
|
||||||
pub mod pdu_metadata;
|
pub mod pdu_metadata;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod short;
|
pub mod short;
|
||||||
|
pub mod spaces;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod state_accessor;
|
pub mod state_accessor;
|
||||||
pub mod state_cache;
|
pub mod state_cache;
|
||||||
pub mod state_compressor;
|
pub mod state_compressor;
|
||||||
|
pub mod threads;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ pub trait Data:
|
||||||
+ state_cache::Data
|
+ state_cache::Data
|
||||||
+ state_compressor::Data
|
+ state_compressor::Data
|
||||||
+ timeline::Data
|
+ timeline::Data
|
||||||
|
+ threads::Data
|
||||||
+ user::Data
|
+ user::Data
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -53,5 +56,7 @@ pub struct Service {
|
||||||
pub state_cache: state_cache::Service,
|
pub state_cache: state_cache::Service,
|
||||||
pub state_compressor: state_compressor::Service,
|
pub state_compressor: state_compressor::Service,
|
||||||
pub timeline: timeline::Service,
|
pub timeline: timeline::Service,
|
||||||
|
pub threads: threads::Service,
|
||||||
|
pub spaces: spaces::Service,
|
||||||
pub user: user::Service,
|
pub user: user::Service,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{service::rooms::timeline::PduCount, PduEvent, Result};
|
||||||
use ruma::{EventId, RoomId};
|
use ruma::{EventId, RoomId, UserId};
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
|
fn add_relation(&self, from: u64, to: u64) -> Result<()>;
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn relations_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
room_id: u64,
|
||||||
|
target: u64,
|
||||||
|
until: PduCount,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
||||||
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()>;
|
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()>;
|
||||||
fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool>;
|
fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool>;
|
||||||
fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()>;
|
fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()>;
|
||||||
|
|
|
@ -2,15 +2,172 @@ mod data;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use ruma::{EventId, RoomId};
|
use ruma::{
|
||||||
|
api::client::relations::get_relating_events,
|
||||||
|
events::{relation::RelationType, TimelineEventType},
|
||||||
|
EventId, RoomId, UserId,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{services, PduEvent, Result};
|
||||||
|
|
||||||
|
use super::timeline::PduCount;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct ExtractRelType {
|
||||||
|
rel_type: RelationType,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct ExtractRelatesToEventId {
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: ExtractRelType,
|
||||||
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
#[tracing::instrument(skip(self, from, to))]
|
||||||
|
pub fn add_relation(&self, from: PduCount, to: PduCount) -> Result<()> {
|
||||||
|
match (from, to) {
|
||||||
|
(PduCount::Normal(f), PduCount::Normal(t)) => self.db.add_relation(f, t),
|
||||||
|
_ => {
|
||||||
|
// TODO: Relations with backfilled pdus
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn paginate_relations_with_filter(
|
||||||
|
&self,
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
target: &EventId,
|
||||||
|
filter_event_type: Option<TimelineEventType>,
|
||||||
|
filter_rel_type: Option<RelationType>,
|
||||||
|
from: PduCount,
|
||||||
|
to: Option<PduCount>,
|
||||||
|
limit: usize,
|
||||||
|
) -> Result<get_relating_events::v1::Response> {
|
||||||
|
let next_token;
|
||||||
|
|
||||||
|
//TODO: Fix ruma: match body.dir {
|
||||||
|
match ruma::api::Direction::Backward {
|
||||||
|
ruma::api::Direction::Forward => {
|
||||||
|
let events_after: Vec<_> = services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.relations_until(sender_user, room_id, target, from)? // TODO: should be relations_after
|
||||||
|
.filter(|r| {
|
||||||
|
r.as_ref().map_or(true, |(_, pdu)| {
|
||||||
|
filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t)
|
||||||
|
&& if let Ok(content) =
|
||||||
|
serde_json::from_str::<ExtractRelatesToEventId>(
|
||||||
|
pdu.content.get(),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
filter_rel_type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |r| &content.relates_to.rel_type == r)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.take(limit)
|
||||||
|
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
next_token = events_after.last().map(|(count, _)| count).copied();
|
||||||
|
|
||||||
|
let events_after: Vec<_> = events_after
|
||||||
|
.into_iter()
|
||||||
|
.rev() // relations are always most recent first
|
||||||
|
.map(|(_, pdu)| pdu.to_message_like_event())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(get_relating_events::v1::Response {
|
||||||
|
chunk: events_after,
|
||||||
|
next_batch: next_token.map(|t| t.stringify()),
|
||||||
|
prev_batch: Some(from.stringify()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ruma::api::Direction::Backward => {
|
||||||
|
let events_before: Vec<_> = services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.relations_until(sender_user, room_id, target, from)?
|
||||||
|
.filter(|r| {
|
||||||
|
r.as_ref().map_or(true, |(_, pdu)| {
|
||||||
|
filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t)
|
||||||
|
&& if let Ok(content) =
|
||||||
|
serde_json::from_str::<ExtractRelatesToEventId>(
|
||||||
|
pdu.content.get(),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
filter_rel_type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |r| &content.relates_to.rel_type == r)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.take(limit)
|
||||||
|
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
next_token = events_before.last().map(|(count, _)| count).copied();
|
||||||
|
|
||||||
|
let events_before: Vec<_> = events_before
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, pdu)| pdu.to_message_like_event())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(get_relating_events::v1::Response {
|
||||||
|
chunk: events_before,
|
||||||
|
next_batch: next_token.map(|t| t.stringify()),
|
||||||
|
prev_batch: Some(from.stringify()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relations_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
room_id: &'a RoomId,
|
||||||
|
target: &'a EventId,
|
||||||
|
until: PduCount,
|
||||||
|
) -> Result<impl Iterator<Item = Result<(PduCount, PduEvent)>> + 'a> {
|
||||||
|
let room_id = services().rooms.short.get_or_create_shortroomid(room_id)?;
|
||||||
|
let target = match services().rooms.timeline.get_pdu_count(target)? {
|
||||||
|
Some(PduCount::Normal(c)) => c,
|
||||||
|
// TODO: Support backfilled relations
|
||||||
|
_ => 0, // This will result in an empty iterator
|
||||||
|
};
|
||||||
|
self.db.relations_until(user_id, room_id, target, until)
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, room_id, event_ids))]
|
#[tracing::instrument(skip(self, room_id, event_ids))]
|
||||||
pub fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()> {
|
pub fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()> {
|
||||||
self.db.mark_as_referenced(room_id, event_ids)
|
self.db.mark_as_referenced(room_id, event_ids)
|
||||||
|
|
|
@ -4,6 +4,7 @@ use ruma::RoomId;
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn index_pdu(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()>;
|
fn index_pdu(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()>;
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn search_pdus<'a>(
|
fn search_pdus<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
|
|
504
src/service/rooms/spaces/mod.rs
Normal file
504
src/service/rooms/spaces/mod.rs
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use lru_cache::LruCache;
|
||||||
|
use ruma::{
|
||||||
|
api::{
|
||||||
|
client::{
|
||||||
|
error::ErrorKind,
|
||||||
|
space::{get_hierarchy, SpaceHierarchyRoomsChunk},
|
||||||
|
},
|
||||||
|
federation,
|
||||||
|
},
|
||||||
|
events::{
|
||||||
|
room::{
|
||||||
|
avatar::RoomAvatarEventContent,
|
||||||
|
canonical_alias::RoomCanonicalAliasEventContent,
|
||||||
|
create::RoomCreateEventContent,
|
||||||
|
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||||
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
|
join_rules::{self, AllowRule, JoinRule, RoomJoinRulesEventContent},
|
||||||
|
topic::RoomTopicEventContent,
|
||||||
|
},
|
||||||
|
space::child::SpaceChildEventContent,
|
||||||
|
StateEventType,
|
||||||
|
},
|
||||||
|
space::SpaceRoomJoinRule,
|
||||||
|
OwnedRoomId, RoomId, UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
|
use crate::{services, Error, PduEvent, Result};
|
||||||
|
|
||||||
|
pub enum CachedJoinRule {
|
||||||
|
//Simplified(SpaceRoomJoinRule),
|
||||||
|
Full(JoinRule),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CachedSpaceChunk {
|
||||||
|
chunk: SpaceHierarchyRoomsChunk,
|
||||||
|
children: Vec<OwnedRoomId>,
|
||||||
|
join_rule: CachedJoinRule,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Service {
|
||||||
|
pub roomid_spacechunk_cache: Mutex<LruCache<OwnedRoomId, Option<CachedSpaceChunk>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
pub async fn get_hierarchy(
|
||||||
|
&self,
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
limit: usize,
|
||||||
|
skip: usize,
|
||||||
|
max_depth: usize,
|
||||||
|
suggested_only: bool,
|
||||||
|
) -> Result<get_hierarchy::v1::Response> {
|
||||||
|
let mut left_to_skip = skip;
|
||||||
|
|
||||||
|
let mut rooms_in_path = Vec::new();
|
||||||
|
let mut stack = vec![vec![room_id.to_owned()]];
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
while let Some(current_room) = {
|
||||||
|
while stack.last().map_or(false, |s| s.is_empty()) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
if !stack.is_empty() {
|
||||||
|
stack.last_mut().and_then(|s| s.pop())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
rooms_in_path.push(current_room.clone());
|
||||||
|
if results.len() >= limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cached) = self
|
||||||
|
.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(¤t_room.to_owned())
|
||||||
|
.as_ref()
|
||||||
|
{
|
||||||
|
if let Some(cached) = cached {
|
||||||
|
let allowed = match &cached.join_rule {
|
||||||
|
//CachedJoinRule::Simplified(s) => {
|
||||||
|
//self.handle_simplified_join_rule(s, sender_user, ¤t_room)?
|
||||||
|
//}
|
||||||
|
CachedJoinRule::Full(f) => {
|
||||||
|
self.handle_join_rule(f, sender_user, ¤t_room)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if allowed {
|
||||||
|
if left_to_skip > 0 {
|
||||||
|
left_to_skip -= 1;
|
||||||
|
} else {
|
||||||
|
results.push(cached.chunk.clone());
|
||||||
|
}
|
||||||
|
if rooms_in_path.len() < max_depth {
|
||||||
|
stack.push(cached.children.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(current_shortstatehash) = services()
|
||||||
|
.rooms
|
||||||
|
.state
|
||||||
|
.get_room_shortstatehash(¤t_room)?
|
||||||
|
{
|
||||||
|
let state = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.state_full_ids(current_shortstatehash)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut children_ids = Vec::new();
|
||||||
|
let mut children_pdus = Vec::new();
|
||||||
|
for (key, id) in state {
|
||||||
|
let (event_type, state_key) =
|
||||||
|
services().rooms.short.get_statekey_from_short(key)?;
|
||||||
|
if event_type != StateEventType::SpaceChild {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pdu = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu(&id)?
|
||||||
|
.ok_or_else(|| Error::bad_database("Event in space state not found"))?;
|
||||||
|
|
||||||
|
if serde_json::from_str::<SpaceChildEventContent>(pdu.content.get())
|
||||||
|
.ok()
|
||||||
|
.map(|c| c.via)
|
||||||
|
.map_or(true, |v| v.is_empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(room_id) = OwnedRoomId::try_from(state_key) {
|
||||||
|
children_ids.push(room_id);
|
||||||
|
children_pdus.push(pdu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Sort children
|
||||||
|
children_ids.reverse();
|
||||||
|
|
||||||
|
let chunk = self.get_room_chunk(sender_user, ¤t_room, children_pdus);
|
||||||
|
if let Ok(chunk) = chunk {
|
||||||
|
if left_to_skip > 0 {
|
||||||
|
left_to_skip -= 1;
|
||||||
|
} else {
|
||||||
|
results.push(chunk.clone());
|
||||||
|
}
|
||||||
|
let join_rule = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(¤t_room, &StateEventType::RoomJoinRules, "")?
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Invalid room join rule event in database: {}", e);
|
||||||
|
Error::BadDatabase("Invalid room join rule event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(JoinRule::Invite);
|
||||||
|
|
||||||
|
self.roomid_spacechunk_cache.lock().unwrap().insert(
|
||||||
|
current_room.clone(),
|
||||||
|
Some(CachedSpaceChunk {
|
||||||
|
chunk,
|
||||||
|
children: children_ids.clone(),
|
||||||
|
join_rule: CachedJoinRule::Full(join_rule),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rooms_in_path.len() < max_depth {
|
||||||
|
stack.push(children_ids);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let server = current_room
|
||||||
|
.server_name()
|
||||||
|
.expect("Room IDs should always have a server name");
|
||||||
|
if server == services().globals.server_name() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !results.is_empty() {
|
||||||
|
// Early return so the client can see some data already
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
debug!("Asking {server} for /hierarchy");
|
||||||
|
if let Ok(response) = services()
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
server,
|
||||||
|
federation::space::get_hierarchy::v1::Request {
|
||||||
|
room_id: current_room.to_owned(),
|
||||||
|
suggested_only,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("Got response from {server} for /hierarchy\n{response:?}");
|
||||||
|
let chunk = SpaceHierarchyRoomsChunk {
|
||||||
|
canonical_alias: response.room.canonical_alias,
|
||||||
|
name: response.room.name,
|
||||||
|
num_joined_members: response.room.num_joined_members,
|
||||||
|
room_id: response.room.room_id,
|
||||||
|
topic: response.room.topic,
|
||||||
|
world_readable: response.room.world_readable,
|
||||||
|
guest_can_join: response.room.guest_can_join,
|
||||||
|
avatar_url: response.room.avatar_url,
|
||||||
|
join_rule: response.room.join_rule.clone(),
|
||||||
|
room_type: response.room.room_type,
|
||||||
|
children_state: response.room.children_state,
|
||||||
|
};
|
||||||
|
let children = response
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.room_id.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let join_rule = match response.room.join_rule {
|
||||||
|
SpaceRoomJoinRule::Invite => JoinRule::Invite,
|
||||||
|
SpaceRoomJoinRule::Knock => JoinRule::Knock,
|
||||||
|
SpaceRoomJoinRule::Private => JoinRule::Private,
|
||||||
|
SpaceRoomJoinRule::Restricted => {
|
||||||
|
JoinRule::Restricted(join_rules::Restricted {
|
||||||
|
allow: response
|
||||||
|
.room
|
||||||
|
.allowed_room_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(AllowRule::room_membership)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
SpaceRoomJoinRule::KnockRestricted => {
|
||||||
|
JoinRule::KnockRestricted(join_rules::Restricted {
|
||||||
|
allow: response
|
||||||
|
.room
|
||||||
|
.allowed_room_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(AllowRule::room_membership)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
SpaceRoomJoinRule::Public => JoinRule::Public,
|
||||||
|
_ => return Err(Error::BadServerResponse("Unknown join rule")),
|
||||||
|
};
|
||||||
|
if self.handle_join_rule(&join_rule, sender_user, ¤t_room)? {
|
||||||
|
if left_to_skip > 0 {
|
||||||
|
left_to_skip -= 1;
|
||||||
|
} else {
|
||||||
|
results.push(chunk.clone());
|
||||||
|
}
|
||||||
|
if rooms_in_path.len() < max_depth {
|
||||||
|
stack.push(children.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.roomid_spacechunk_cache.lock().unwrap().insert(
|
||||||
|
current_room.clone(),
|
||||||
|
Some(CachedSpaceChunk {
|
||||||
|
chunk,
|
||||||
|
children,
|
||||||
|
join_rule: CachedJoinRule::Full(join_rule),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
for child in response.children {
|
||||||
|
roomid_spacechunk_cache.insert(
|
||||||
|
current_room.clone(),
|
||||||
|
CachedSpaceChunk {
|
||||||
|
chunk: child.chunk,
|
||||||
|
children,
|
||||||
|
join_rule,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
self.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(current_room.clone(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(get_hierarchy::v1::Response {
|
||||||
|
next_batch: if results.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((skip + results.len()).to_string())
|
||||||
|
},
|
||||||
|
rooms: results,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_room_chunk(
|
||||||
|
&self,
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
children: Vec<Arc<PduEvent>>,
|
||||||
|
) -> Result<SpaceHierarchyRoomsChunk> {
|
||||||
|
Ok(SpaceHierarchyRoomsChunk {
|
||||||
|
canonical_alias: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCanonicalAlias, "")?
|
||||||
|
.map_or(Ok(None), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomCanonicalAliasEventContent| c.alias)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid canonical alias event in database.")
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
name: services().rooms.state_accessor.get_name(room_id)?,
|
||||||
|
num_joined_members: services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_joined_count(room_id)?
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!("Room {} has no member count", room_id);
|
||||||
|
0
|
||||||
|
})
|
||||||
|
.try_into()
|
||||||
|
.expect("user count should not be that big"),
|
||||||
|
room_id: room_id.to_owned(),
|
||||||
|
topic: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomTopic, "")?
|
||||||
|
.map_or(Ok(None), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomTopicEventContent| Some(c.topic))
|
||||||
|
.map_err(|_| {
|
||||||
|
error!("Invalid room topic event in database for room {}", room_id);
|
||||||
|
Error::bad_database("Invalid room topic event in database.")
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
world_readable: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
|
.map_or(Ok(false), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||||
|
c.history_visibility == HistoryVisibility::WorldReadable
|
||||||
|
})
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database(
|
||||||
|
"Invalid room history visibility event in database.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
guest_can_join: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomGuestAccess, "")?
|
||||||
|
.map_or(Ok(false), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomGuestAccessEventContent| {
|
||||||
|
c.guest_access == GuestAccess::CanJoin
|
||||||
|
})
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid room guest access event in database.")
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
avatar_url: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomAvatar, "")?
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomAvatarEventContent| c.url)
|
||||||
|
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
// url is now an Option<String> so we must flatten
|
||||||
|
.flatten(),
|
||||||
|
join_rule: {
|
||||||
|
let join_rule = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomJoinRules, "")?
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Invalid room join rule event in database: {}", e);
|
||||||
|
Error::BadDatabase("Invalid room join rule event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(JoinRule::Invite);
|
||||||
|
|
||||||
|
if !self.handle_join_rule(&join_rule, sender_user, room_id)? {
|
||||||
|
debug!("User is not allowed to see room {room_id}");
|
||||||
|
// This error will be caught later
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::Forbidden,
|
||||||
|
"User is not allowed to see the room",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.translate_joinrule(&join_rule)?
|
||||||
|
},
|
||||||
|
room_type: services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
|
||||||
|
error!("Invalid room create event in database: {}", e);
|
||||||
|
Error::BadDatabase("Invalid room create event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.and_then(|e| e.room_type),
|
||||||
|
children_state: children
|
||||||
|
.into_iter()
|
||||||
|
.map(|pdu| pdu.to_stripped_spacechild_state_event())
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_joinrule(&self, join_rule: &JoinRule) -> Result<SpaceRoomJoinRule> {
|
||||||
|
match join_rule {
|
||||||
|
JoinRule::Invite => Ok(SpaceRoomJoinRule::Invite),
|
||||||
|
JoinRule::Knock => Ok(SpaceRoomJoinRule::Knock),
|
||||||
|
JoinRule::Private => Ok(SpaceRoomJoinRule::Private),
|
||||||
|
JoinRule::Restricted(_) => Ok(SpaceRoomJoinRule::Restricted),
|
||||||
|
JoinRule::KnockRestricted(_) => Ok(SpaceRoomJoinRule::KnockRestricted),
|
||||||
|
JoinRule::Public => Ok(SpaceRoomJoinRule::Public),
|
||||||
|
_ => Err(Error::BadServerResponse("Unknown join rule")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_simplified_join_rule(
|
||||||
|
&self,
|
||||||
|
join_rule: &SpaceRoomJoinRule,
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let allowed = match join_rule {
|
||||||
|
SpaceRoomJoinRule::Public => true,
|
||||||
|
SpaceRoomJoinRule::Knock => true,
|
||||||
|
SpaceRoomJoinRule::Invite => services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(sender_user, room_id)?,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(allowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_join_rule(
|
||||||
|
&self,
|
||||||
|
join_rule: &JoinRule,
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<bool> {
|
||||||
|
if self.handle_simplified_join_rule(
|
||||||
|
&self.translate_joinrule(join_rule)?,
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
)? {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
match join_rule {
|
||||||
|
JoinRule::Restricted(r) => {
|
||||||
|
for rule in &r.allow {
|
||||||
|
if let join_rules::AllowRule::RoomMembership(rm) = rule {
|
||||||
|
if let Ok(true) = services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(sender_user, &rm.room_id)
|
||||||
|
{
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
JoinRule::KnockRestricted(_) => {
|
||||||
|
// TODO: Check rules
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
_ => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,10 @@ use std::{
|
||||||
|
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
api::client::error::ErrorKind,
|
||||||
events::{
|
events::{
|
||||||
room::{create::RoomCreateEventContent, member::MembershipState},
|
room::{create::RoomCreateEventContent, member::MembershipState},
|
||||||
AnyStrippedStateEvent, RoomEventType, StateEventType,
|
AnyStrippedStateEvent, StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
state_res::{self, StateMap},
|
state_res::{self, StateMap},
|
||||||
|
@ -32,15 +33,15 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
shortstatehash: u64,
|
shortstatehash: u64,
|
||||||
statediffnew: HashSet<CompressedStateEvent>,
|
statediffnew: Arc<HashSet<CompressedStateEvent>>,
|
||||||
_statediffremoved: HashSet<CompressedStateEvent>,
|
_statediffremoved: Arc<HashSet<CompressedStateEvent>>,
|
||||||
state_lock: &MutexGuard<'_, ()>, // Take mutex guard to make sure users get the room state mutex
|
state_lock: &MutexGuard<'_, ()>, // Take mutex guard to make sure users get the room state mutex
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for event_id in statediffnew.into_iter().filter_map(|new| {
|
for event_id in statediffnew.iter().filter_map(|new| {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.parse_compressed_state_event(&new)
|
.parse_compressed_state_event(new)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(_, id)| id)
|
.map(|(_, id)| id)
|
||||||
}) {
|
}) {
|
||||||
|
@ -49,10 +50,6 @@ impl Service {
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if pdu.get("type").and_then(|val| val.as_str()) != Some("m.room.member") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pdu: PduEvent = match serde_json::from_str(
|
let pdu: PduEvent = match serde_json::from_str(
|
||||||
&serde_json::to_string(&pdu).expect("CanonicalJsonObj can be serialized to JSON"),
|
&serde_json::to_string(&pdu).expect("CanonicalJsonObj can be serialized to JSON"),
|
||||||
) {
|
) {
|
||||||
|
@ -60,12 +57,15 @@ impl Service {
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match pdu.kind {
|
||||||
|
TimelineEventType::RoomMember => {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExtractMembership {
|
struct ExtractMembership {
|
||||||
membership: MembershipState,
|
membership: MembershipState,
|
||||||
}
|
}
|
||||||
|
|
||||||
let membership = match serde_json::from_str::<ExtractMembership>(pdu.content.get()) {
|
let membership =
|
||||||
|
match serde_json::from_str::<ExtractMembership>(pdu.content.get()) {
|
||||||
Ok(e) => e.membership,
|
Ok(e) => e.membership,
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
@ -89,6 +89,18 @@ impl Service {
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
TimelineEventType::SpaceChild => {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.spaces
|
||||||
|
.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&pdu.room_id);
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
services().rooms.state_cache.update_joined_count(room_id)?;
|
services().rooms.state_cache.update_joined_count(room_id)?;
|
||||||
|
|
||||||
|
@ -107,7 +119,7 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
event_id: &EventId,
|
event_id: &EventId,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
state_ids_compressed: HashSet<CompressedStateEvent>,
|
state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
||||||
) -> Result<u64> {
|
) -> Result<u64> {
|
||||||
let shorteventid = services()
|
let shorteventid = services()
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -152,9 +164,9 @@ impl Service {
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(statediffnew, statediffremoved)
|
(Arc::new(statediffnew), Arc::new(statediffremoved))
|
||||||
} else {
|
} else {
|
||||||
(state_ids_compressed, HashSet::new())
|
(state_ids_compressed, Arc::new(HashSet::new()))
|
||||||
};
|
};
|
||||||
services().rooms.state_compressor.save_state_from_diff(
|
services().rooms.state_compressor.save_state_from_diff(
|
||||||
shortstatehash,
|
shortstatehash,
|
||||||
|
@ -234,8 +246,8 @@ impl Service {
|
||||||
|
|
||||||
services().rooms.state_compressor.save_state_from_diff(
|
services().rooms.state_compressor.save_state_from_diff(
|
||||||
shortstatehash,
|
shortstatehash,
|
||||||
statediffnew,
|
Arc::new(statediffnew),
|
||||||
statediffremoved,
|
Arc::new(statediffremoved),
|
||||||
2,
|
2,
|
||||||
states_parents,
|
states_parents,
|
||||||
)?;
|
)?;
|
||||||
|
@ -320,7 +332,7 @@ impl Service {
|
||||||
"",
|
"",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let create_event_content: Option<RoomCreateEventContent> = create_event
|
let create_event_content: RoomCreateEventContent = create_event
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|create_event| {
|
.map(|create_event| {
|
||||||
serde_json::from_str(create_event.content.get()).map_err(|e| {
|
serde_json::from_str(create_event.content.get()).map_err(|e| {
|
||||||
|
@ -328,11 +340,10 @@ impl Service {
|
||||||
Error::bad_database("Invalid create event in db.")
|
Error::bad_database("Invalid create event in db.")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?
|
||||||
let room_version = create_event_content
|
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "No create event found"))?;
|
||||||
.map(|create_event| create_event.room_version)
|
|
||||||
.ok_or(Error::BadDatabase("Invalid room version"))?;
|
Ok(create_event_content.room_version)
|
||||||
Ok(room_version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_room_shortstatehash(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
pub fn get_room_shortstatehash(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||||
|
@ -358,7 +369,7 @@ impl Service {
|
||||||
pub fn get_auth_events(
|
pub fn get_auth_events(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
kind: &RoomEventType,
|
kind: &TimelineEventType,
|
||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
state_key: Option<&str>,
|
state_key: Option<&str>,
|
||||||
content: &serde_json::value::RawValue,
|
content: &serde_json::value::RawValue,
|
||||||
|
@ -396,12 +407,12 @@ impl Service {
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
Ok(full_state
|
Ok(full_state
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|compressed| {
|
.filter_map(|compressed| {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.parse_compressed_state_event(&compressed)
|
.parse_compressed_state_event(compressed)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
.filter_map(|(shortstatekey, event_id)| {
|
.filter_map(|(shortstatekey, event_id)| {
|
||||||
|
|
|
@ -9,12 +9,14 @@ use lru_cache::LruCache;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
|
avatar::RoomAvatarEventContent,
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
|
name::RoomNameEventContent,
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
EventId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -178,7 +180,7 @@ impl Service {
|
||||||
return Ok(*visibility);
|
return Ok(*visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
let currently_member = services().rooms.state_cache.is_joined(&user_id, &room_id)?;
|
let currently_member = services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||||
|
|
||||||
let history_visibility = self
|
let history_visibility = self
|
||||||
.state_get(shortstatehash, &StateEventType::RoomHistoryVisibility, "")?
|
.state_get(shortstatehash, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
|
@ -195,11 +197,11 @@ impl Service {
|
||||||
HistoryVisibility::Shared => currently_member,
|
HistoryVisibility::Shared => currently_member,
|
||||||
HistoryVisibility::Invited => {
|
HistoryVisibility::Invited => {
|
||||||
// Allow if any member on requesting server was AT LEAST invited, else deny
|
// Allow if any member on requesting server was AT LEAST invited, else deny
|
||||||
self.user_was_invited(shortstatehash, &user_id)
|
self.user_was_invited(shortstatehash, user_id)
|
||||||
}
|
}
|
||||||
HistoryVisibility::Joined => {
|
HistoryVisibility::Joined => {
|
||||||
// Allow if any member on requested server was joined, else deny
|
// Allow if any member on requested server was joined, else deny
|
||||||
self.user_was_joined(shortstatehash, &user_id)
|
self.user_was_joined(shortstatehash, user_id)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unknown history visibility {history_visibility}");
|
error!("Unknown history visibility {history_visibility}");
|
||||||
|
@ -219,10 +221,10 @@ impl Service {
|
||||||
/// the room's history_visibility at that event's state.
|
/// the room's history_visibility at that event's state.
|
||||||
#[tracing::instrument(skip(self, user_id, room_id))]
|
#[tracing::instrument(skip(self, user_id, room_id))]
|
||||||
pub fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
pub fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||||
let currently_member = services().rooms.state_cache.is_joined(&user_id, &room_id)?;
|
let currently_member = services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||||
|
|
||||||
let history_visibility = self
|
let history_visibility = self
|
||||||
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
|
.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||||
serde_json::from_str(s.content.get())
|
serde_json::from_str(s.content.get())
|
||||||
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||||
|
@ -269,4 +271,48 @@ impl Service {
|
||||||
) -> Result<Option<Arc<PduEvent>>> {
|
) -> Result<Option<Arc<PduEvent>>> {
|
||||||
self.db.room_state_get(room_id, event_type, state_key)
|
self.db.room_state_get(room_id, event_type, state_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomName, "")?
|
||||||
|
.map_or(Ok(None), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomNameEventContent| Some(c.name))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(
|
||||||
|
"Invalid room name event in database for room {}. {}",
|
||||||
|
room_id, e
|
||||||
|
);
|
||||||
|
Error::bad_database("Invalid room name event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_avatar(&self, room_id: &RoomId) -> Result<JsOption<RoomAvatarEventContent>> {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomAvatar, "")?
|
||||||
|
.map_or(Ok(JsOption::Undefined), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_member(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
user_id: &UserId,
|
||||||
|
) -> Result<Option<RoomMemberEventContent>> {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
||||||
|
.map_or(Ok(None), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map_err(|_| Error::bad_database("Invalid room member event in database."))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ pub trait Data: Send + Sync {
|
||||||
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user was invited to.
|
/// Returns an iterator over all rooms a user was invited to.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn rooms_invited<'a>(
|
fn rooms_invited<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
|
@ -96,6 +97,7 @@ pub trait Data: Send + Sync {
|
||||||
) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
|
) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user left.
|
/// Returns an iterator over all rooms a user left.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn rooms_left<'a>(
|
fn rooms_left<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
|
|
|
@ -14,6 +14,7 @@ use ruma::{
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{services, Error, Result};
|
use crate::{services, Error, Result};
|
||||||
|
|
||||||
|
@ -88,8 +89,9 @@ impl Service {
|
||||||
RoomAccountDataEventType::Tag,
|
RoomAccountDataEventType::Tag,
|
||||||
)?
|
)?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str(event.get()).map_err(|_| {
|
serde_json::from_str(event.get()).map_err(|e| {
|
||||||
Error::bad_database("Invalid account data event in db.")
|
warn!("Invalid account data event in db: {e:?}");
|
||||||
|
Error::BadDatabase("Invalid account data event in db.")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
@ -113,8 +115,9 @@ impl Service {
|
||||||
GlobalAccountDataEventType::Direct.to_string().into(),
|
GlobalAccountDataEventType::Direct.to_string().into(),
|
||||||
)?
|
)?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str::<DirectEvent>(event.get()).map_err(|_| {
|
serde_json::from_str::<DirectEvent>(event.get()).map_err(|e| {
|
||||||
Error::bad_database("Invalid account data event in db.")
|
warn!("Invalid account data event in db: {e:?}");
|
||||||
|
Error::BadDatabase("Invalid account data event in db.")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
@ -155,8 +158,10 @@ impl Service {
|
||||||
.into(),
|
.into(),
|
||||||
)?
|
)?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str::<IgnoredUserListEvent>(event.get())
|
serde_json::from_str::<IgnoredUserListEvent>(event.get()).map_err(|e| {
|
||||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))
|
warn!("Invalid account data event in db: {e:?}");
|
||||||
|
Error::BadDatabase("Invalid account data event in db.")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map_or(false, |ignored| {
|
.map_or(false, |ignored| {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
use super::CompressedStateEvent;
|
use super::CompressedStateEvent;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
pub struct StateDiff {
|
pub struct StateDiff {
|
||||||
pub parent: Option<u64>,
|
pub parent: Option<u64>,
|
||||||
pub added: HashSet<CompressedStateEvent>,
|
pub added: Arc<HashSet<CompressedStateEvent>>,
|
||||||
pub removed: HashSet<CompressedStateEvent>,
|
pub removed: Arc<HashSet<CompressedStateEvent>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
|
|
|
@ -16,14 +16,15 @@ use self::data::StateDiff;
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub stateinfo_cache: Mutex<
|
pub stateinfo_cache: Mutex<
|
||||||
LruCache<
|
LruCache<
|
||||||
u64,
|
u64,
|
||||||
Vec<(
|
Vec<(
|
||||||
u64, // sstatehash
|
u64, // sstatehash
|
||||||
HashSet<CompressedStateEvent>, // full state
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
HashSet<CompressedStateEvent>, // added
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
HashSet<CompressedStateEvent>, // removed
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
|
@ -33,6 +34,7 @@ pub type CompressedStateEvent = [u8; 2 * size_of::<u64>()];
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
/// Returns a stack with info on shortstatehash, full state, added diff and removed diff for the selected shortstatehash and each parent layer.
|
/// Returns a stack with info on shortstatehash, full state, added diff and removed diff for the selected shortstatehash and each parent layer.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn load_shortstatehash_info(
|
pub fn load_shortstatehash_info(
|
||||||
&self,
|
&self,
|
||||||
|
@ -40,9 +42,9 @@ impl Service {
|
||||||
) -> Result<
|
) -> Result<
|
||||||
Vec<(
|
Vec<(
|
||||||
u64, // sstatehash
|
u64, // sstatehash
|
||||||
HashSet<CompressedStateEvent>, // full state
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
HashSet<CompressedStateEvent>, // added
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
HashSet<CompressedStateEvent>, // removed
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
if let Some(r) = self
|
if let Some(r) = self
|
||||||
|
@ -62,13 +64,19 @@ impl Service {
|
||||||
|
|
||||||
if let Some(parent) = parent {
|
if let Some(parent) = parent {
|
||||||
let mut response = self.load_shortstatehash_info(parent)?;
|
let mut response = self.load_shortstatehash_info(parent)?;
|
||||||
let mut state = response.last().unwrap().1.clone();
|
let mut state = (*response.last().unwrap().1).clone();
|
||||||
state.extend(added.iter().copied());
|
state.extend(added.iter().copied());
|
||||||
|
let removed = (*removed).clone();
|
||||||
for r in &removed {
|
for r in &removed {
|
||||||
state.remove(r);
|
state.remove(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.push((shortstatehash, state, added, removed));
|
response.push((shortstatehash, Arc::new(state), added, Arc::new(removed)));
|
||||||
|
|
||||||
|
self.stateinfo_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(shortstatehash, response.clone());
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,6 +133,7 @@ impl Service {
|
||||||
/// * `statediffremoved` - Removed from base. Each vec is shortstatekey+shorteventid
|
/// * `statediffremoved` - Removed from base. Each vec is shortstatekey+shorteventid
|
||||||
/// * `diff_to_sibling` - Approximately how much the diff grows each time for this layer
|
/// * `diff_to_sibling` - Approximately how much the diff grows each time for this layer
|
||||||
/// * `parent_states` - A stack with info on shortstatehash, full state, added diff and removed diff for each parent layer
|
/// * `parent_states` - A stack with info on shortstatehash, full state, added diff and removed diff for each parent layer
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
#[tracing::instrument(skip(
|
#[tracing::instrument(skip(
|
||||||
self,
|
self,
|
||||||
statediffnew,
|
statediffnew,
|
||||||
|
@ -135,14 +144,14 @@ impl Service {
|
||||||
pub fn save_state_from_diff(
|
pub fn save_state_from_diff(
|
||||||
&self,
|
&self,
|
||||||
shortstatehash: u64,
|
shortstatehash: u64,
|
||||||
statediffnew: HashSet<CompressedStateEvent>,
|
statediffnew: Arc<HashSet<CompressedStateEvent>>,
|
||||||
statediffremoved: HashSet<CompressedStateEvent>,
|
statediffremoved: Arc<HashSet<CompressedStateEvent>>,
|
||||||
diff_to_sibling: usize,
|
diff_to_sibling: usize,
|
||||||
mut parent_states: Vec<(
|
mut parent_states: Vec<(
|
||||||
u64, // sstatehash
|
u64, // sstatehash
|
||||||
HashSet<CompressedStateEvent>, // full state
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
HashSet<CompressedStateEvent>, // added
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
HashSet<CompressedStateEvent>, // removed
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
)>,
|
)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let diffsum = statediffnew.len() + statediffremoved.len();
|
let diffsum = statediffnew.len() + statediffremoved.len();
|
||||||
|
@ -152,29 +161,29 @@ impl Service {
|
||||||
// To many layers, we have to go deeper
|
// To many layers, we have to go deeper
|
||||||
let parent = parent_states.pop().unwrap();
|
let parent = parent_states.pop().unwrap();
|
||||||
|
|
||||||
let mut parent_new = parent.2;
|
let mut parent_new = (*parent.2).clone();
|
||||||
let mut parent_removed = parent.3;
|
let mut parent_removed = (*parent.3).clone();
|
||||||
|
|
||||||
for removed in statediffremoved {
|
for removed in statediffremoved.iter() {
|
||||||
if !parent_new.remove(&removed) {
|
if !parent_new.remove(removed) {
|
||||||
// It was not added in the parent and we removed it
|
// It was not added in the parent and we removed it
|
||||||
parent_removed.insert(removed);
|
parent_removed.insert(*removed);
|
||||||
}
|
}
|
||||||
// Else it was added in the parent and we removed it again. We can forget this change
|
// Else it was added in the parent and we removed it again. We can forget this change
|
||||||
}
|
}
|
||||||
|
|
||||||
for new in statediffnew {
|
for new in statediffnew.iter() {
|
||||||
if !parent_removed.remove(&new) {
|
if !parent_removed.remove(new) {
|
||||||
// It was not touched in the parent and we added it
|
// It was not touched in the parent and we added it
|
||||||
parent_new.insert(new);
|
parent_new.insert(*new);
|
||||||
}
|
}
|
||||||
// Else it was removed in the parent and we added it again. We can forget this change
|
// Else it was removed in the parent and we added it again. We can forget this change
|
||||||
}
|
}
|
||||||
|
|
||||||
self.save_state_from_diff(
|
self.save_state_from_diff(
|
||||||
shortstatehash,
|
shortstatehash,
|
||||||
parent_new,
|
Arc::new(parent_new),
|
||||||
parent_removed,
|
Arc::new(parent_removed),
|
||||||
diffsum,
|
diffsum,
|
||||||
parent_states,
|
parent_states,
|
||||||
)?;
|
)?;
|
||||||
|
@ -205,29 +214,29 @@ impl Service {
|
||||||
|
|
||||||
if diffsum * diffsum >= 2 * diff_to_sibling * parent_diff {
|
if diffsum * diffsum >= 2 * diff_to_sibling * parent_diff {
|
||||||
// Diff too big, we replace above layer(s)
|
// Diff too big, we replace above layer(s)
|
||||||
let mut parent_new = parent.2;
|
let mut parent_new = (*parent.2).clone();
|
||||||
let mut parent_removed = parent.3;
|
let mut parent_removed = (*parent.3).clone();
|
||||||
|
|
||||||
for removed in statediffremoved {
|
for removed in statediffremoved.iter() {
|
||||||
if !parent_new.remove(&removed) {
|
if !parent_new.remove(removed) {
|
||||||
// It was not added in the parent and we removed it
|
// It was not added in the parent and we removed it
|
||||||
parent_removed.insert(removed);
|
parent_removed.insert(*removed);
|
||||||
}
|
}
|
||||||
// Else it was added in the parent and we removed it again. We can forget this change
|
// Else it was added in the parent and we removed it again. We can forget this change
|
||||||
}
|
}
|
||||||
|
|
||||||
for new in statediffnew {
|
for new in statediffnew.iter() {
|
||||||
if !parent_removed.remove(&new) {
|
if !parent_removed.remove(new) {
|
||||||
// It was not touched in the parent and we added it
|
// It was not touched in the parent and we added it
|
||||||
parent_new.insert(new);
|
parent_new.insert(*new);
|
||||||
}
|
}
|
||||||
// Else it was removed in the parent and we added it again. We can forget this change
|
// Else it was removed in the parent and we added it again. We can forget this change
|
||||||
}
|
}
|
||||||
|
|
||||||
self.save_state_from_diff(
|
self.save_state_from_diff(
|
||||||
shortstatehash,
|
shortstatehash,
|
||||||
parent_new,
|
Arc::new(parent_new),
|
||||||
parent_removed,
|
Arc::new(parent_removed),
|
||||||
diffsum,
|
diffsum,
|
||||||
parent_states,
|
parent_states,
|
||||||
)?;
|
)?;
|
||||||
|
@ -247,14 +256,15 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the new shortstatehash, and the state diff from the previous room state
|
/// Returns the new shortstatehash, and the state diff from the previous room state
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn save_state(
|
pub fn save_state(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
new_state_ids_compressed: HashSet<CompressedStateEvent>,
|
new_state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
u64,
|
u64,
|
||||||
HashSet<CompressedStateEvent>,
|
Arc<HashSet<CompressedStateEvent>>,
|
||||||
HashSet<CompressedStateEvent>,
|
Arc<HashSet<CompressedStateEvent>>,
|
||||||
)> {
|
)> {
|
||||||
let previous_shortstatehash = services().rooms.state.get_room_shortstatehash(room_id)?;
|
let previous_shortstatehash = services().rooms.state.get_room_shortstatehash(room_id)?;
|
||||||
|
|
||||||
|
@ -271,7 +281,11 @@ impl Service {
|
||||||
.get_or_create_shortstatehash(&state_hash)?;
|
.get_or_create_shortstatehash(&state_hash)?;
|
||||||
|
|
||||||
if Some(new_shortstatehash) == previous_shortstatehash {
|
if Some(new_shortstatehash) == previous_shortstatehash {
|
||||||
return Ok((new_shortstatehash, HashSet::new(), HashSet::new()));
|
return Ok((
|
||||||
|
new_shortstatehash,
|
||||||
|
Arc::new(HashSet::new()),
|
||||||
|
Arc::new(HashSet::new()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let states_parents = previous_shortstatehash
|
let states_parents = previous_shortstatehash
|
||||||
|
@ -290,9 +304,9 @@ impl Service {
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(statediffnew, statediffremoved)
|
(Arc::new(statediffnew), Arc::new(statediffremoved))
|
||||||
} else {
|
} else {
|
||||||
(new_state_ids_compressed, HashSet::new())
|
(new_state_ids_compressed, Arc::new(HashSet::new()))
|
||||||
};
|
};
|
||||||
|
|
||||||
if !already_existed {
|
if !already_existed {
|
||||||
|
|
16
src/service/rooms/threads/data.rs
Normal file
16
src/service/rooms/threads/data.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::{PduEvent, Result};
|
||||||
|
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
||||||
|
|
||||||
|
pub trait Data: Send + Sync {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn threads_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
room_id: &'a RoomId,
|
||||||
|
until: u64,
|
||||||
|
include: &'a IncludeThreads,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>;
|
||||||
|
|
||||||
|
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()>;
|
||||||
|
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>>;
|
||||||
|
}
|
116
src/service/rooms/threads/mod.rs
Normal file
116
src/service/rooms/threads/mod.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
mod data;
|
||||||
|
|
||||||
|
pub use data::Data;
|
||||||
|
use ruma::{
|
||||||
|
api::client::{error::ErrorKind, threads::get_threads::v1::IncludeThreads},
|
||||||
|
events::relation::BundledThread,
|
||||||
|
uint, CanonicalJsonValue, EventId, RoomId, UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{services, Error, PduEvent, Result};
|
||||||
|
|
||||||
|
pub struct Service {
|
||||||
|
pub db: &'static dyn Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
pub fn threads_until<'a>(
|
||||||
|
&'a self,
|
||||||
|
user_id: &'a UserId,
|
||||||
|
room_id: &'a RoomId,
|
||||||
|
until: u64,
|
||||||
|
include: &'a IncludeThreads,
|
||||||
|
) -> Result<impl Iterator<Item = Result<(u64, PduEvent)>> + 'a> {
|
||||||
|
self.db.threads_until(user_id, room_id, until, include)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_thread(&self, root_event_id: &EventId, pdu: &PduEvent) -> Result<()> {
|
||||||
|
let root_id = &services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_id(root_event_id)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Invalid event id in thread message",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let root_pdu = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_from_id(root_id)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut root_pdu_json = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_json_from_id(root_id)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let CanonicalJsonValue::Object(unsigned) = root_pdu_json
|
||||||
|
.entry("unsigned".to_owned())
|
||||||
|
.or_insert_with(|| CanonicalJsonValue::Object(Default::default()))
|
||||||
|
{
|
||||||
|
if let Some(mut relations) = unsigned
|
||||||
|
.get("m.relations")
|
||||||
|
.and_then(|r| r.as_object())
|
||||||
|
.and_then(|r| r.get("m.thread"))
|
||||||
|
.and_then(|relations| {
|
||||||
|
serde_json::from_value::<BundledThread>(relations.clone().into()).ok()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// Thread already existed
|
||||||
|
relations.count += uint!(1);
|
||||||
|
relations.latest_event = pdu.to_message_like_event();
|
||||||
|
|
||||||
|
let content = serde_json::to_value(relations).expect("to_value always works");
|
||||||
|
|
||||||
|
unsigned.insert(
|
||||||
|
"m.relations".to_owned(),
|
||||||
|
json!({ "m.thread": content })
|
||||||
|
.try_into()
|
||||||
|
.expect("thread is valid json"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// New thread
|
||||||
|
let relations = BundledThread {
|
||||||
|
latest_event: pdu.to_message_like_event(),
|
||||||
|
count: uint!(1),
|
||||||
|
current_user_participated: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = serde_json::to_value(relations).expect("to_value always works");
|
||||||
|
|
||||||
|
unsigned.insert(
|
||||||
|
"m.relations".to_owned(),
|
||||||
|
json!({ "m.thread": content })
|
||||||
|
.try_into()
|
||||||
|
.expect("thread is valid json"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.replace_pdu(root_id, &root_pdu_json, &root_pdu)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut users = Vec::new();
|
||||||
|
if let Some(userids) = self.db.get_participants(root_id)? {
|
||||||
|
users.extend_from_slice(&userids);
|
||||||
|
users.push(pdu.sender.clone());
|
||||||
|
} else {
|
||||||
|
users.push(root_pdu.sender);
|
||||||
|
users.push(pdu.sender.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.update_participants(root_id, &users)
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,10 +57,16 @@ pub trait Data: Send + Sync {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Removes a pdu and creates a new one with the same id.
|
/// Removes a pdu and creates a new one with the same id.
|
||||||
fn replace_pdu(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<()>;
|
fn replace_pdu(
|
||||||
|
&self,
|
||||||
|
pdu_id: &[u8],
|
||||||
|
pdu_json: &CanonicalJsonObject,
|
||||||
|
pdu: &PduEvent,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||||
/// event with id `until` in reverse-chronological order.
|
/// event with id `until` in reverse-chronological order.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn pdus_until<'a>(
|
fn pdus_until<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
|
@ -70,6 +76,7 @@ pub trait Data: Send + Sync {
|
||||||
|
|
||||||
/// Returns an iterator over all events in a room that happened after the event with id `from`
|
/// Returns an iterator over all events in a room that happened after the event with id `from`
|
||||||
/// in chronological order.
|
/// in chronological order.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn pdus_after<'a>(
|
fn pdus_after<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
|
|
|
@ -1,44 +1,42 @@
|
||||||
mod data;
|
mod data;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::{
|
||||||
use std::collections::{BTreeMap, HashMap};
|
cmp::Ordering,
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
};
|
||||||
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ruma::api::federation;
|
|
||||||
use ruma::serde::Base64;
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::error::ErrorKind,
|
api::{client::error::ErrorKind, federation},
|
||||||
canonical_json::to_canonical_value,
|
canonical_json::to_canonical_value,
|
||||||
events::{
|
events::{
|
||||||
push_rules::PushRulesEvent,
|
push_rules::PushRulesEvent,
|
||||||
room::{
|
room::{
|
||||||
create::RoomCreateEventContent, member::MembershipState,
|
create::RoomCreateEventContent, encrypted::Relation, member::MembershipState,
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
},
|
},
|
||||||
GlobalAccountDataEventType, RoomEventType, StateEventType,
|
GlobalAccountDataEventType, StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
push::{Action, Ruleset, Tweak},
|
push::{Action, Ruleset, Tweak},
|
||||||
|
serde::Base64,
|
||||||
state_res,
|
state_res,
|
||||||
state_res::Event,
|
state_res::{Event, RoomVersion},
|
||||||
state_res::RoomVersion,
|
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||||
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId,
|
||||||
OwnedServerName, RoomAliasId, RoomId, UserId,
|
|
||||||
};
|
};
|
||||||
use ruma::{user_id, ServerName};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||||
use tokio::sync::MutexGuard;
|
use tokio::sync::MutexGuard;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::api::server_server;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
api::server_server,
|
||||||
service::pdu::{EventHash, PduBuilder},
|
service::pdu::{EventHash, PduBuilder},
|
||||||
services, utils, Error, PduEvent, Result,
|
services, utils, Error, PduEvent, Result,
|
||||||
};
|
};
|
||||||
|
@ -60,8 +58,8 @@ impl PduCount {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_string(token: &str) -> Result<Self> {
|
pub fn try_from_string(token: &str) -> Result<Self> {
|
||||||
if token.starts_with('-') {
|
if let Some(stripped) = token.strip_prefix('-') {
|
||||||
token[1..].parse().map(PduCount::Backfilled)
|
stripped.parse().map(PduCount::Backfilled)
|
||||||
} else {
|
} else {
|
||||||
token.parse().map(PduCount::Normal)
|
token.parse().map(PduCount::Normal)
|
||||||
}
|
}
|
||||||
|
@ -92,18 +90,6 @@ impl Ord for PduCount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn comparisons() {
|
|
||||||
assert!(PduCount::Normal(1) < PduCount::Normal(2));
|
|
||||||
assert!(PduCount::Backfilled(2) < PduCount::Backfilled(1));
|
|
||||||
assert!(PduCount::Normal(1) > PduCount::Backfilled(1));
|
|
||||||
assert!(PduCount::Backfilled(1) < PduCount::Normal(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
|
@ -114,7 +100,7 @@ pub struct Service {
|
||||||
impl Service {
|
impl Service {
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn first_pdu_in_room(&self, room_id: &RoomId) -> Result<Option<Arc<PduEvent>>> {
|
pub fn first_pdu_in_room(&self, room_id: &RoomId) -> Result<Option<Arc<PduEvent>>> {
|
||||||
self.all_pdus(&user_id!("@doesntmatter:conduit.rs"), &room_id)?
|
self.all_pdus(user_id!("@doesntmatter:conduit.rs"), room_id)?
|
||||||
.next()
|
.next()
|
||||||
.map(|o| o.map(|(_, p)| Arc::new(p)))
|
.map(|o| o.map(|(_, p)| Arc::new(p)))
|
||||||
.transpose()
|
.transpose()
|
||||||
|
@ -199,8 +185,13 @@ impl Service {
|
||||||
|
|
||||||
/// Removes a pdu and creates a new one with the same id.
|
/// Removes a pdu and creates a new one with the same id.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn replace_pdu(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<()> {
|
pub fn replace_pdu(
|
||||||
self.db.replace_pdu(pdu_id, pdu)
|
&self,
|
||||||
|
pdu_id: &[u8],
|
||||||
|
pdu_json: &CanonicalJsonObject,
|
||||||
|
pdu: &PduEvent,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.db.replace_pdu(pdu_id, pdu_json, pdu)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new persisted data unit and adds it to a room.
|
/// Creates a new persisted data unit and adds it to a room.
|
||||||
|
@ -317,12 +308,25 @@ impl Service {
|
||||||
let mut notifies = Vec::new();
|
let mut notifies = Vec::new();
|
||||||
let mut highlights = Vec::new();
|
let mut highlights = Vec::new();
|
||||||
|
|
||||||
for user in services()
|
let mut push_target = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.get_our_real_users(&pdu.room_id)?
|
.get_our_real_users(&pdu.room_id)?;
|
||||||
.iter()
|
|
||||||
{
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
|
if let Some(state_key) = &pdu.state_key {
|
||||||
|
let target_user_id = UserId::parse(state_key.clone())
|
||||||
|
.expect("This state_key was previously validated");
|
||||||
|
|
||||||
|
if !push_target.contains(&target_user_id) {
|
||||||
|
let mut target = push_target.as_ref().clone();
|
||||||
|
target.insert(target_user_id);
|
||||||
|
push_target = Arc::new(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for user in push_target.iter() {
|
||||||
// Don't notify the user of their own events
|
// Don't notify the user of their own events
|
||||||
if user == &pdu.sender {
|
if user == &pdu.sender {
|
||||||
continue;
|
continue;
|
||||||
|
@ -354,9 +358,7 @@ impl Service {
|
||||||
&pdu.room_id,
|
&pdu.room_id,
|
||||||
)? {
|
)? {
|
||||||
match action {
|
match action {
|
||||||
Action::DontNotify => notify = false,
|
Action::Notify => notify = true,
|
||||||
// TODO: Implement proper support for coalesce
|
|
||||||
Action::Notify | Action::Coalesce => notify = true,
|
|
||||||
Action::SetTweak(Tweak::Highlight(true)) => {
|
Action::SetTweak(Tweak::Highlight(true)) => {
|
||||||
highlight = true;
|
highlight = true;
|
||||||
}
|
}
|
||||||
|
@ -381,12 +383,23 @@ impl Service {
|
||||||
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
|
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
|
||||||
|
|
||||||
match pdu.kind {
|
match pdu.kind {
|
||||||
RoomEventType::RoomRedaction => {
|
TimelineEventType::RoomRedaction => {
|
||||||
if let Some(redact_id) = &pdu.redacts {
|
if let Some(redact_id) = &pdu.redacts {
|
||||||
self.redact_pdu(redact_id, pdu)?;
|
self.redact_pdu(redact_id, pdu)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomEventType::RoomMember => {
|
TimelineEventType::SpaceChild => {
|
||||||
|
if let Some(_state_key) = &pdu.state_key {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.spaces
|
||||||
|
.roomid_spacechunk_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&pdu.room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TimelineEventType::RoomMember => {
|
||||||
if let Some(state_key) = &pdu.state_key {
|
if let Some(state_key) = &pdu.state_key {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExtractMembership {
|
struct ExtractMembership {
|
||||||
|
@ -420,7 +433,7 @@ impl Service {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomEventType::RoomMessage => {
|
TimelineEventType::RoomMessage => {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExtractBody {
|
struct ExtractBody {
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
|
@ -443,7 +456,10 @@ impl Service {
|
||||||
)?;
|
)?;
|
||||||
let server_user = format!("@conduit:{}", services().globals.server_name());
|
let server_user = format!("@conduit:{}", services().globals.server_name());
|
||||||
|
|
||||||
let to_conduit = body.starts_with(&format!("{server_user}: "));
|
let to_conduit = body.starts_with(&format!("{server_user}: "))
|
||||||
|
|| body.starts_with(&format!("{server_user} "))
|
||||||
|
|| body == format!("{server_user}:")
|
||||||
|
|| body == server_user;
|
||||||
|
|
||||||
// This will evaluate to false if the emergency password is set up so that
|
// This will evaluate to false if the emergency password is set up so that
|
||||||
// the administrator can execute commands as conduit
|
// the administrator can execute commands as conduit
|
||||||
|
@ -458,6 +474,62 @@ impl Service {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Relationships
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ExtractRelatesTo {
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: Relation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct ExtractEventId {
|
||||||
|
event_id: OwnedEventId,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct ExtractRelatesToEventId {
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: ExtractEventId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(content) = serde_json::from_str::<ExtractRelatesToEventId>(pdu.content.get()) {
|
||||||
|
if let Some(related_pducount) = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_count(&content.relates_to.event_id)?
|
||||||
|
{
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.add_relation(PduCount::Normal(count2), related_pducount)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(content) = serde_json::from_str::<ExtractRelatesTo>(pdu.content.get()) {
|
||||||
|
match content.relates_to {
|
||||||
|
Relation::Reply { in_reply_to } => {
|
||||||
|
// We need to do it again here, because replies don't have
|
||||||
|
// event_id as a top level field
|
||||||
|
if let Some(related_pducount) = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_count(&in_reply_to.event_id)?
|
||||||
|
{
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.pdu_metadata
|
||||||
|
.add_relation(PduCount::Normal(count2), related_pducount)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Relation::Thread(thread) => {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.threads
|
||||||
|
.add_to_thread(&thread.event_id, pdu)?;
|
||||||
|
}
|
||||||
|
_ => {} // TODO: Aggregate other types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for appservice in services().appservice.all()? {
|
for appservice in services().appservice.all()? {
|
||||||
if services()
|
if services()
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -472,7 +544,7 @@ impl Service {
|
||||||
|
|
||||||
// If the RoomMember event has a non-empty state_key, it is targeted at someone.
|
// If the RoomMember event has a non-empty state_key, it is targeted at someone.
|
||||||
// If it is our appservice user, we send this PDU to it.
|
// If it is our appservice user, we send this PDU to it.
|
||||||
if pdu.kind == RoomEventType::RoomMember {
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
if let Some(state_key_uid) = &pdu
|
if let Some(state_key_uid) = &pdu
|
||||||
.state_key
|
.state_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -522,7 +594,7 @@ impl Service {
|
||||||
|
|
||||||
let matching_users = |users: &Regex| {
|
let matching_users = |users: &Regex| {
|
||||||
users.is_match(pdu.sender.as_str())
|
users.is_match(pdu.sender.as_str())
|
||||||
|| pdu.kind == RoomEventType::RoomMember
|
|| pdu.kind == TimelineEventType::RoomMember
|
||||||
&& pdu
|
&& pdu
|
||||||
.state_key
|
.state_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -756,14 +828,14 @@ impl Service {
|
||||||
)?;
|
)?;
|
||||||
if admin_room.filter(|v| v == room_id).is_some() {
|
if admin_room.filter(|v| v == room_id).is_some() {
|
||||||
match pdu.event_type() {
|
match pdu.event_type() {
|
||||||
RoomEventType::RoomEncryption => {
|
TimelineEventType::RoomEncryption => {
|
||||||
warn!("Encryption is not allowed in the admins room");
|
warn!("Encryption is not allowed in the admins room");
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
"Encryption is not allowed in the admins room.",
|
"Encryption is not allowed in the admins room.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
RoomEventType::RoomMember => {
|
TimelineEventType::RoomMember => {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExtractMembership {
|
struct ExtractMembership {
|
||||||
membership: MembershipState,
|
membership: MembershipState,
|
||||||
|
@ -771,7 +843,7 @@ impl Service {
|
||||||
|
|
||||||
let target = pdu
|
let target = pdu
|
||||||
.state_key()
|
.state_key()
|
||||||
.filter(|v| v.starts_with("@"))
|
.filter(|v| v.starts_with('@'))
|
||||||
.unwrap_or(sender.as_str());
|
.unwrap_or(sender.as_str());
|
||||||
let server_name = services().globals.server_name();
|
let server_name = services().globals.server_name();
|
||||||
let server_user = format!("@conduit:{}", server_name);
|
let server_user = format!("@conduit:{}", server_name);
|
||||||
|
@ -779,7 +851,7 @@ impl Service {
|
||||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||||
|
|
||||||
if content.membership == MembershipState::Leave {
|
if content.membership == MembershipState::Leave {
|
||||||
if target == &server_user {
|
if target == server_user {
|
||||||
warn!("Conduit user cannot leave from admins room");
|
warn!("Conduit user cannot leave from admins room");
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -805,7 +877,7 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
|
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
|
||||||
if target == &server_user {
|
if target == server_user {
|
||||||
warn!("Conduit user cannot be banned in admins room");
|
warn!("Conduit user cannot be banned in admins room");
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -862,7 +934,7 @@ impl Service {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// In case we are kicking or banning a user, we need to inform their server of the change
|
// In case we are kicking or banning a user, we need to inform their server of the change
|
||||||
if pdu.kind == RoomEventType::RoomMember {
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
if let Some(state_key_uid) = &pdu
|
if let Some(state_key_uid) = &pdu
|
||||||
.state_key
|
.state_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -888,7 +960,7 @@ impl Service {
|
||||||
pdu: &PduEvent,
|
pdu: &PduEvent,
|
||||||
pdu_json: CanonicalJsonObject,
|
pdu_json: CanonicalJsonObject,
|
||||||
new_room_leaves: Vec<OwnedEventId>,
|
new_room_leaves: Vec<OwnedEventId>,
|
||||||
state_ids_compressed: HashSet<CompressedStateEvent>,
|
state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
||||||
soft_fail: bool,
|
soft_fail: bool,
|
||||||
state_lock: &MutexGuard<'_, ()>, // Take mutex guard to make sure users get the room state mutex
|
state_lock: &MutexGuard<'_, ()>, // Take mutex guard to make sure users get the room state mutex
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
|
@ -958,12 +1030,17 @@ impl Service {
|
||||||
/// Replace a PDU with the redacted form.
|
/// Replace a PDU with the redacted form.
|
||||||
#[tracing::instrument(skip(self, reason))]
|
#[tracing::instrument(skip(self, reason))]
|
||||||
pub fn redact_pdu(&self, event_id: &EventId, reason: &PduEvent) -> Result<()> {
|
pub fn redact_pdu(&self, event_id: &EventId, reason: &PduEvent) -> Result<()> {
|
||||||
|
// TODO: Don't reserialize, keep original json
|
||||||
if let Some(pdu_id) = self.get_pdu_id(event_id)? {
|
if let Some(pdu_id) = self.get_pdu_id(event_id)? {
|
||||||
let mut pdu = self
|
let mut pdu = self
|
||||||
.get_pdu_from_id(&pdu_id)?
|
.get_pdu_from_id(&pdu_id)?
|
||||||
.ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?;
|
.ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?;
|
||||||
pdu.redact(reason)?;
|
pdu.redact(reason)?;
|
||||||
self.replace_pdu(&pdu_id, &pdu)?;
|
self.replace_pdu(
|
||||||
|
&pdu_id,
|
||||||
|
&utils::to_canonical_object(&pdu).expect("PDU is an object"),
|
||||||
|
&pdu,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
// If event does not exist, just noop
|
// If event does not exist, just noop
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -972,7 +1049,7 @@ impl Service {
|
||||||
#[tracing::instrument(skip(self, room_id))]
|
#[tracing::instrument(skip(self, room_id))]
|
||||||
pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Result<()> {
|
pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Result<()> {
|
||||||
let first_pdu = self
|
let first_pdu = self
|
||||||
.all_pdus(&user_id!("@doesntmatter:conduit.rs"), &room_id)?
|
.all_pdus(user_id!("@doesntmatter:conduit.rs"), room_id)?
|
||||||
.next()
|
.next()
|
||||||
.expect("Room is not empty")?;
|
.expect("Room is not empty")?;
|
||||||
|
|
||||||
|
@ -984,7 +1061,7 @@ impl Service {
|
||||||
let power_levels: RoomPowerLevelsEventContent = services()
|
let power_levels: RoomPowerLevelsEventContent = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
|
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
||||||
.map(|ev| {
|
.map(|ev| {
|
||||||
serde_json::from_str(ev.content.get())
|
serde_json::from_str(ev.content.get())
|
||||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
||||||
|
@ -1015,11 +1092,9 @@ impl Service {
|
||||||
.await;
|
.await;
|
||||||
match response {
|
match response {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let mut pub_key_map = RwLock::new(BTreeMap::new());
|
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||||
for pdu in response.pdus {
|
for pdu in response.pdus {
|
||||||
if let Err(e) = self
|
if let Err(e) = self.backfill_pdu(backfill_server, pdu, &pub_key_map).await
|
||||||
.backfill_pdu(backfill_server, pdu, &mut pub_key_map)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
warn!("Failed to add backfilled pdu: {e}");
|
warn!("Failed to add backfilled pdu: {e}");
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1141,7 @@ impl Service {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
.handle_incoming_pdu(origin, &event_id, &room_id, value, false, &pub_key_map)
|
.handle_incoming_pdu(origin, &event_id, &room_id, value, false, pub_key_map)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let value = self.get_pdu_json(&event_id)?.expect("We just created it");
|
let value = self.get_pdu_json(&event_id)?.expect("We just created it");
|
||||||
|
@ -1099,8 +1174,7 @@ impl Service {
|
||||||
|
|
||||||
drop(insert_lock);
|
drop(insert_lock);
|
||||||
|
|
||||||
match pdu.kind {
|
if pdu.kind == TimelineEventType::RoomMessage {
|
||||||
RoomEventType::RoomMessage => {
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExtractBody {
|
struct ExtractBody {
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
|
@ -1116,11 +1190,22 @@ impl Service {
|
||||||
.index_pdu(shortroomid, &pdu_id, &body)?;
|
.index_pdu(shortroomid, &pdu_id, &body)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
drop(mutex_lock);
|
drop(mutex_lock);
|
||||||
|
|
||||||
info!("Prepended backfill pdu");
|
info!("Prepended backfill pdu");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comparisons() {
|
||||||
|
assert!(PduCount::Normal(1) < PduCount::Normal(2));
|
||||||
|
assert!(PduCount::Backfilled(2) < PduCount::Backfilled(1));
|
||||||
|
assert!(PduCount::Normal(1) > PduCount::Backfilled(1));
|
||||||
|
assert!(PduCount::Backfilled(1) < PduCount::Normal(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue