Compare commits

...

103 commits
v0.6.0 ... next

Author SHA1 Message Date
Timo Kösters
99ab234f40
Merge branch 'fixes' into 'next'
Avoid panic when client is confused about rooms

See merge request famedly/conduit!588
2024-02-28 16:19:48 +00:00
Timo Kösters
e83416bb5a
Merge branch 'fixnginx' into 'next'
Fixed nginx proxy_pass directive

See merge request famedly/conduit!589
2024-02-28 16:09:55 +00:00
olly1240
726b6f0fa6
Fixed nginx proxy_pass directive 2024-02-28 16:38:06 +01:00
Timo Kösters
d7fd89df49
fix: avoid panic when client is confused about rooms 2024-02-28 16:31:41 +01:00
Timo Kösters
f4e57fdb22
Avoid federation when it is not necessary 2024-02-28 16:27:08 +01:00
Timo Kösters
4f096adcfa Merge branch 'bump-ruma' into 'next'
Bump ruma to latest commit

See merge request famedly/conduit!586
2024-02-25 19:35:54 +00:00
21a5fa3ef0 refactor: use re-exported JsOption from ruma rather than directly adding it as a dependency 2024-02-25 10:30:30 +00:00
b27e9ea95c chore: bump ruma to latest commit (as of 2024-02-25) 2024-02-25 08:49:20 +00:00
8aa915acb9 bump ruma, support deprecated user login field 2024-02-23 20:29:17 +00:00
ace9637bc2 replace unwraps with expects 2024-02-23 19:39:30 +00:00
Charles Hall
be1e2e9307 Merge branch 'ci/push-dockerhub' into 'next'
feat(ci): push oci-image to docker hub

See merge request famedly/conduit!584
2024-02-18 01:36:50 +00:00
Samuel Meenzen
1c6a4b1b24 feat(ci): push oci-image to docker hub 2024-02-18 01:36:50 +00:00
976a73a0e5 style: appease rustfmt 2024-02-16 21:19:40 +00:00
4c06f329c4 refactor: appease clippy 2024-02-16 21:13:59 +00:00
d841b81c56 chore: update Cargo.lock 2024-02-16 20:52:19 +00:00
e707084345 chore: bump ruma to latest commit (as of 2024-02-16) 2024-02-16 20:52:07 +00:00
strawberry
6dcc8b6cf1 bump ruma to latest commit (syncv3 JsOption and push optional power levels)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-02-16 20:45:48 +00:00
strawberry
a2ac491c54 bump ruma, add wrong room keys error code, tiny logging change
can't update ruma to very latest commit because of the weird JsOption thing for syncv4 that i can't wrap my head around how to use, not important anyways

Signed-off-by: strawberry <strawberry@pupbrain.dev>
2024-02-16 20:45:27 +00:00
Charles Hall
72a13d8353 Merge branch 'flake-compat' into 'next'
support non-flake users

See merge request famedly/conduit!581
2024-02-02 03:32:41 +00:00
Raito Bezarius
3a63f9dfb6
feat: support non-flake users
This uses flakes-compat to read the `flake.nix` and expose it
to non-flake users.

Signed-off-by: Raito Bezarius <masterancpp@gmail.com>
2024-02-01 19:19:56 -08:00
Timo Kösters
f4f2d05b5b Merge branch 'update-ring' into 'next'
update ring to ^0.17

See merge request famedly/conduit!580
2024-02-01 10:45:28 +00:00
Charles Hall
c3c7bcb2ed Merge branch 'publish-oci-image' into 'next'
Publish oci image to the gitlab registry

See merge request famedly/conduit!570
2024-01-30 17:04:48 +00:00
Samuel Meenzen
d6c57f9b2e Publish oci image to the gitlab registry 2024-01-30 17:04:47 +00:00
Charles Hall
7fb9e99649
update ring and jsonwebtoken to remove ring ^0.16 2024-01-29 16:21:42 -08:00
Charles Hall
1274b48ebb
run cargo update
`IndexMap::remove` was deprecated in favor of explicitly named methods.
I assume that we actually needed to be using `shift_remove`, otherwise
we probably wouldn't be bothering with `indexmap` here in the first
place. I wonder if this fixes any bugs lol
2024-01-29 16:17:25 -08:00
Charles Hall
0a281e81a5 Merge branch 'fix-oci-image-cross' into 'next'
pass pkgsCrossStatic to mkOciImage, not pkgsHost

See merge request famedly/conduit!579
2024-01-29 23:50:56 +00:00
Charles Hall
a43bde69fa
pass pkgsCrossStatic to mkOciImage, not pkgsHost
This fixes a bug where the aarch64 OCI image had metadata saying it was
an x86_64 OCI image. On top of that, I think the metadata was actually
right (aside from Conduit's binary): since all other packages were being
pulled from `pkgsHost`, an OCI image cross compiled for aarch64 from a
different architecture would result in unexecutable binaries (e.g. tini)
since they were compiled for the completely wrong architecture.
2024-01-29 15:39:09 -08:00
Charles Hall
986343877c Merge branch 'artifact-links' into 'next'
update DEPLOY.md with new build links

See merge request famedly/conduit!578
2024-01-29 23:04:24 +00:00
Charles Hall
2d47710b55
update DEPLOY.md with new build links 2024-01-29 14:55:48 -08:00
Charles Hall
10542a1d70 Merge branch 'use-upstream-crane' into 'next'
switch crane input back to upstream

See merge request famedly/conduit!576
2024-01-28 21:54:38 +00:00
Charles Hall
c167f7a6ad
switch crane input back to upstream
Thanks to the crane maintainer to fixing my issue in a way that doesn't
suck, unlike my attempt in the fork we were briefly using.
2024-01-28 13:31:03 -08:00
Charles Hall
5787a70bab Merge branch 'fix-complement' into 'next'
make complement (mostly) work again

See merge request famedly/conduit!575
2024-01-28 03:21:04 +00:00
Charles Hall
cf8f1f2546
make a bunch of changes so complement works again
Well, kinda. It crashed on me after 10 minutes because the tests timed
out like in <https://github.com/matrix-org/complement/issues/394>.
Sounds like this means it's a them problem though.

I want to use Nix to build this image instead in the future but this
will at least make it work for now and give me a reference for while I'm
porting it. I also want to make Conduit natively understand Complement's
requirements instead of `sed`ing a bunch of stuff and needing a reverse
proxy in the container. Should be more reliable that way.

I'm not making this run in CI until the above stuff is addressed and
until I can decide on a way to pin the revision of Complement being
tested against.
2024-01-27 18:09:43 -08:00
Charles Hall
3c2fc4a4c6 Merge branch 'oci-image-ca-certs' into 'next'
add ca certificates to the OCI image

See merge request famedly/conduit!574
2024-01-27 20:37:21 +00:00
Charles Hall
dffd771e7c
add ca certificates to the OCI image
Without this, checking the authority of TLS certificates fails, making
Conduit (rightly) refuse to connect to anything.
2024-01-27 12:25:06 -08:00
Charles Hall
4da8c7e282 Merge branch 'docker-tag' into 'next'
change docker tag back to `next`

See merge request famedly/conduit!573
2024-01-27 05:55:08 +00:00
Charles Hall
0df5d18fd6
change docker tag back to next
I misunderstood what the general meaning of the `latest` tag was.
2024-01-26 21:38:13 -08:00
Charles Hall
825ceac1c3 Merge branch 'update-rust' into 'next'
update rust toolchain

See merge request famedly/conduit!572
2024-01-26 18:27:47 +00:00
Charles Hall
3e389256f5
switch lint config to manifest-lint feature
I removed some lint configuration in the process:

* `#[allow(clippy::suspicious_else_formatting)]` because nothing is
  currently triggering it.
* `#[warn(clippy::future_not_send)]` because some stuff under
  `src/lib.rs` is. And also like, auto-trait leakage generally means
  this isn't a problem, and if things really need to be `Send`, then
  you'll probably know to mark it manually.
* `#[warn(rust_2018_idioms)]` and replaced it with
  `explicit-outlives-requirements = "warn"` which is the most useful
  lint in that group that isn't enabled by default.
2024-01-26 01:03:55 -08:00
Charles Hall
a7892a28ec
refer directly to stdenv since it's in scope 2024-01-25 22:00:32 -08:00
Charles Hall
9453dbc740
update rust toolchain
It comes with a bunch of new lints (yay!) so I fixed them all so CI will
keep working.

Also apparently something about linking changed because I had to change
the checks for deciding the linker flags for static x86_64 builds to
keep working.
2024-01-25 21:44:40 -08:00
Charles Hall
bf48c10d28 Merge branch 'cross' into 'next'
cross compile static binaries for x86_64 and aarch64

See merge request famedly/conduit!569
2024-01-26 04:12:15 +00:00
Charles Hall
7c1a3e41d9
add package to build an aarch64 oci image
And build it as an artifact in CI.
2024-01-25 20:03:29 -08:00
Charles Hall
2a04a361e0
break oci image builder into a function
Now it can be reused for different `pkgs` and `package`s.
2024-01-25 20:03:25 -08:00
Charles Hall
0e8e4f1083
add static cross to aarch64-unknown-linux-musl 2024-01-25 19:44:06 -08:00
Charles Hall
81ae579b25
add static cross to x86_64-unknown-linux-musl 2024-01-25 19:43:23 -08:00
Charles Hall
3a3cafe912
preempt cross problems by using my crane fork
I imagine this will get fixed/merged upstream in the near future.
2024-01-25 11:39:17 -08:00
Charles Hall
d29591d47d
group packages in attrset literal
This will make generating packages for cross possible.
2024-01-25 11:39:17 -08:00
Charles Hall
67d280dd2e
factor package expression into a function
We'll need to call it repeatedly to make packages for cross.
2024-01-25 11:39:17 -08:00
Charles Hall
3ac9be5a78
add x86_64-unknown-linux-gnu
This is probably the most common target and usually doesn't involve
cross compilation.
2024-01-25 11:39:17 -08:00
Charles Hall
52954f7a11
use fromToolchainFile
I *think* this will make it easier to pull in extra rustc targets.
2024-01-25 11:39:16 -08:00
Charles Hall
692a31620d
make let bindings take pkgs as an argument
Again, will make cross compilation easier to set up.
2024-01-25 11:39:16 -08:00
Charles Hall
cf4015b830
rename pkgs to pkgsHost
This will make organizing cross compilation easier.
2024-01-25 11:39:16 -08:00
Charles Hall
9cef03127b
remove with for nativeBuildInputs
It's going to get more involved and that `with` was too specific.
2024-01-25 11:39:16 -08:00
Charles Hall
249fc7769d
don't bother with mold
For now, at least. I suspect it will make cross compilation more
difficult.
2024-01-25 11:39:16 -08:00
Charles Hall
5cc53c9e14
push oci image and x86_64-*-gnu build to bin cache
This will allow most Nix users to use the `default` package and without
having to build from source. And also allows any weirdos to get the OCI
image from the Nix binary cache if they want. No idea why that would be
desireable though lol
2024-01-25 11:37:35 -08:00
Charles Hall
bdc46f6392
add script to build and push to binary cache
This is even useful for local development, as you can pre-populate the
binary cache before running CI (assuming you have the token). Also, it
being in a script makes it easier to test.

We've added attic as a flake input even though the flake itself doesn't
use it so that we can use `--inputs-from .` in Nix commands to reference
a locked version of attic. This helps with reproducibility and caching,
and to makes it easy to update attic because it's part of the normal
flake lifecycle.
2024-01-25 11:34:46 -08:00
Charles Hall
6ae776218c
add our own binary cache
The machine I'm hosting this on doesn't have incredible upload speeds
but it should be good enough?
2024-01-25 10:51:21 -08:00
Charles Hall
bd2b146d5d
add crane binary cache
This way we don't need to build e.g. crane-utils every time.
2024-01-24 23:25:48 -08:00
Charles Hall
f7cc4fb3bb
state artifacts' targets and rename artifacts
This will make it more obvious what's what and be more internally
consistent.
2024-01-24 23:25:42 -08:00
Charles Hall
ca198c51fa Merge branch 'reqwest-follow-up' into 'next'
move resolver logic into the resolver

See merge request famedly/conduit!571
2024-01-24 23:24:00 +00:00
Charles Hall
fe86d28428
move resolver logic into the resolver
Honestly not sure why it wasn't done like this before. This code is much
less awkward to follow and more compartmentalized.

These changes were mainly motivated by a clippy lint triggering on the
original code, which then made me wonder if I could get rid of some of
the `Box`ing. Turns out I could, and this is the result of that.
2024-01-24 15:11:17 -08:00
Timo Kösters
c86f9a5c5b Merge branch 'pr_upstream_reqwest' into 'next'
Use upstream `reqwest` instead of vendored one

See merge request famedly/conduit!527
2024-01-24 21:59:34 +00:00
Timo Kösters
e0358a9de5 Merge branch 'send_push_to_invited_user' into 'next'
feat: send push notification on invite to invited user and etc

Closes #399

See merge request famedly/conduit!559
2024-01-24 17:55:22 +00:00
Tobias Bucher
69d0003222 Use upstream reqwest instead of vendored one
This uses the `ClientBuilder::dns_resolver` function that was added in
reqwest 0.11.13, instead of the homebrew `ClientBuilder::resolve_fn`.
2024-01-24 17:12:43 +01:00
Timo Kösters
5cf9f3df48 Merge branch 'lints' into 'next'
fix CI

See merge request famedly/conduit!564
2024-01-24 15:42:20 +00:00
Charles Hall
0b7ed5adc9
add debian package building in ci
This uses a separate step and docker image, which I'm not a huge fan of.
At least I could get this to work for now, but I won't be shocked when
it breaks later. I know, I know, fixing this kind of problem is the
exact reason I bothered to do this, but I was really struggling to do
better here. Maybe I can take a second pass at this later.

Also, this explicitly names the caches, because without this, various
things related to linking will break.
2024-01-24 07:22:37 -08:00
Charles Hall
4de54db305
redo docker image and build it in ci 2024-01-24 07:22:37 -08:00
Charles Hall
02781e4f9b
use nix-filter to filter sources
This prevents nix from rebuilding conduit when files that don't actually
effect the build are changed.
2024-01-24 07:22:37 -08:00
Charles Hall
f8bdfd82b0
update flake.lock
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e' (2023-07-07)
  → 'github:ipetkov/crane/742170d82cd65c925dcddc5c3d6185699fbbad08' (2024-01-18)
• Removed input 'crane/flake-compat'
• Removed input 'crane/flake-utils'
• Removed input 'crane/rust-overlay'
• Removed input 'crane/rust-overlay/flake-utils'
• Removed input 'crane/rust-overlay/nixpkgs'
• Updated input 'fenix':
    'github:nix-community/fenix/39096fe3f379036ff4a5fa198950b8e79defe939' (2023-07-16)
  → 'github:nix-community/fenix/e132ea0eb0c799a2109a91688e499d7bf4962801' (2024-01-18)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/996e054f1eb1dbfc8455ecabff0f6ff22ba7f7c8' (2023-07-15)
  → 'github:rust-lang/rust-analyzer/9d9b34354d2f13e33568c9c55b226dd014a146a0' (2024-01-17)
• Updated input 'flake-utils':
    'github:numtide/flake-utils/919d646de7be200f3bf08cb76ae1f09402b6f9b4' (2023-07-11)
  → 'github:numtide/flake-utils/1ef2e671c3b0c19053962c07dbda38332dcebf26' (2024-01-15)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/8acef304efe70152463a6399f73e636bcc363813' (2023-07-15)
  → 'github:NixOS/nixpkgs/842d9d80cfd4560648c785f8a4e6f3b096790e19' (2024-01-17)
2024-01-24 07:22:37 -08:00
Charles Hall
7e66d2e2c0
use nix and engage to manage ci 2024-01-24 07:22:37 -08:00
Charles Hall
ffd03a256b
remove workflow rules
I don't think these are actually necessary? At least in my own testing,
I haven't seen duplicate pipelines for a single commit when converting
from just a branch to a merge request.
2024-01-24 07:22:37 -08:00
Charles Hall
9d592d60d2
remove dockerlint step because it does nothing
It's configured to let the pipeline pass even if there are warnings or
errors, i.e. it's pointless.
2024-01-24 07:22:37 -08:00
Charles Hall
25ceb5ebd8
remove commented out ci step
If you want it back, just look at the git history.
2024-01-24 07:22:37 -08:00
Charles Hall
6f052fff98
improve nix flake
Also fix the comment in `Cargo.toml` about the rust-version stuff.
2024-01-24 07:22:37 -08:00
Charles Hall
e8ac881b2f
add an engage file
See <https://charles.page.computer.surgery/engage/> for info.
2024-01-24 07:22:37 -08:00
Charles Hall
0d17aedae5
fix cargo doc lints
Rustdoc (rightfully) thought the `[commandbody]` "tags" were broken
links, so I've just made them links to nothing instead.
2024-01-24 07:22:37 -08:00
Charles Hall
ab1fff2642
fix cargo clippy lints 2024-01-24 07:22:37 -08:00
Charles Hall
92c5b6b86c
fix cargo check lints 2024-01-24 07:22:25 -08:00
Charles Hall
dc2f53e773
comment out heed backend things
The code in conduit doesn't compile.
2024-01-18 12:27:48 -08:00
Timo Kösters
2475995102 Merge branch 'conduit/declare-1.5-support' into 'next'
declare 1.5 support

See merge request famedly/conduit!568
2024-01-17 17:56:21 +00:00
Charles Hall
835f4ad8cf
declare 1.5 support 2024-01-16 13:52:56 -08:00
Timo Kösters
ca6219723b Merge branch 'conduit/envrc-shebang' into 'next'
add shebang to .envrc

See merge request famedly/conduit!566
2023-12-24 15:36:52 +00:00
Timo Kösters
40c7c248fb Merge branch 'conduit/rm-presence-panic' into 'next'
don't panic on missing presence status for a user

See merge request famedly/conduit!565
2023-12-24 15:36:36 +00:00
Charles Hall
8f3f5c01f9
add shebang to .envrc
All this really does is make syntax highlighting and shellcheck work by
default in more editors.
2023-12-23 22:21:19 -08:00
Charles Hall
9d7f7b871b
don't panic on missing presence status for a user 2023-12-23 21:01:01 -08:00
Timo Kösters
30f0871e21 Merge branch 'sendjoin-signature-error' into 'next'
Log underlying error when rejecting sendjoin response

See merge request famedly/conduit!563
2023-12-11 15:11:42 +00:00
Val Lorentz
98e81c6217 Log underlying error when rejecting sendjoin response 2023-12-03 19:38:09 +01:00
AndSDev
f3b6b3e222 feat: send push notification on invite to invited user and etc 2023-11-07 12:46:53 +00:00
Timo Kösters
3bfdae795d Merge branch 'sliding' into 'next'
Sliding sync improvements and redaction fixes

See merge request famedly/conduit!549
2023-09-13 18:57:57 +00:00
Timo Kösters
75c80df271
Sliding sync improvements and redaction fixes 2023-09-13 20:54:53 +02:00
Timo Kösters
094cb888d4 Merge branch 'badacl' into 'next'
fix: ACL error shouldn't break the whole request

See merge request famedly/conduit!542
2023-09-13 18:46:03 +00:00
Timo Kösters
fa725a14e2 Merge branch 'lukehmcc-next-patch-37096' into 'next'
Update README.md to fix typo & fix compatibility with new versions of docker compose

See merge request famedly/conduit!545
2023-09-11 18:34:06 +00:00
Luke McCarthy
9b3664aeeb Update README.md to fix typo & fix compatibility with new versions of docker compose 2023-08-27 02:14:03 +00:00
Jonas Zohren
90fea00dc7 Merge branch 'docs-docker-coturn' into 'next'
Docs: coturn instructions for docker

See merge request famedly/conduit!498
2023-08-23 14:18:42 +00:00
Jonas Zohren
20924a44f1 Suggestion on how to generate a secure key 2023-08-23 11:17:47 +02:00
purplemeteorite
38d6426b0e coturn setup instructions for docker 2023-08-23 11:09:21 +02:00
Timo Kösters
9b55ce933a
Back off from more events, don't retry auth events 2023-08-12 09:53:32 +02:00
Timo Kösters
f73a657a23
fix: ACL error shouldn't break the whole request 2023-08-11 20:29:22 +02:00
Timo Kösters
6dfb262ddf Merge branch 'patch-3' into 'next'
log handling previous event time as debug

See merge request famedly/conduit!540
2023-08-11 09:27:42 +00:00
Timo Kösters
75cdc3a1f6 Merge branch 'roomversionwarnings' into 'next'
Do not show "Invalid room version" errors when server is not in room

See merge request famedly/conduit!541
2023-08-11 09:27:23 +00:00
Timo Kösters
11103a92ed
Do not show "Invalid room version" errors when server is not in room 2023-08-11 10:48:48 +02:00
girlbossceo
ce2017a10e log handling previous event time as debug
Signed-off-by: girlbossceo <june@girlboss.ceo>
2023-08-10 23:12:37 +00:00
68 changed files with 2338 additions and 1845 deletions

4
.envrc
View file

@ -1 +1,5 @@
#!/usr/bin/env bash
use flake use flake
PATH_add bin

3
.gitignore vendored
View file

@ -68,3 +68,6 @@ cached_target
# Direnv cache # Direnv cache
/.direnv /.direnv
# Gitlab CI cache
/.gitlab-ci.d

View file

@ -1,244 +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
needs: [] - if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=" >> /etc/nix/nix.conf; fi
tags: [ "docker" ]
variables: # Add crane binary cache
# Docker in Docker: - if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
DOCKER_BUILDKIT: 1 - 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
image:
name: docker.io/docker # Add nix-community binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
# 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:
- name: docker.io/docker:dind - docker:25.0.0-dind
alias: docker variables:
IMAGE_SUFFIX_AMD64: amd64
IMAGE_SUFFIX_ARM64V8: arm64v8
script: script:
- apk add openssh-client - docker load -i oci-image-amd64.tar.gz
- eval $(ssh-agent -s) - IMAGE_ID_AMD64=$(docker images -q conduit:next)
- mkdir -p ~/.ssh && chmod 700 ~/.ssh - docker load -i oci-image-arm64v8.tar.gz
- printf "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config - IMAGE_ID_ARM64V8=$(docker images -q conduit:next)
- sh .gitlab/setup-buildx-remote-builders.sh # Tag and push the architecture specific images
# Authorize against this project's own image registry: - docker tag $IMAGE_ID_AMD64 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - docker tag $IMAGE_ID_ARM64V8 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
# Build multiplatform image and push to temporary tag: - docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
- > - docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
docker buildx build # Tag the multi-arch image
--platform "linux/arm/v7,linux/arm64,linux/amd64" - 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
--pull - docker manifest push $IMAGE_NAME:$CI_COMMIT_SHA
--tag "$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID" # Tag and push the git ref
--push - 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
--provenance=false - docker manifest push $IMAGE_NAME:$CI_COMMIT_REF_NAME
--file "Dockerfile" . # Tag git tags as 'latest'
# Build multiplatform image to deb stage and extract their .deb files: - |
- > if [[ -n "$CI_COMMIT_TAG" ]]; then
docker buildx build docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
--platform "linux/arm/v7,linux/arm64,linux/amd64" docker manifest push $IMAGE_NAME:latest
--target "packager-result" fi
--output="type=local,dest=/tmp/build-output" dependencies:
--provenance=false - oci-image:x86_64-unknown-linux-gnu
--file "Dockerfile" . - oci-image:aarch64-unknown-linux-musl
# Build multiplatform image to binary stage and extract their binaries: only:
- > - next
docker buildx build - master
--platform "linux/arm/v7,linux/arm64,linux/amd64" - tags
--target "builder-result"
--output="type=local,dest=/tmp/build-output"
--provenance=false
--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: oci-image:push-gitlab:
extends: .docker-shared-settings extends: .push-oci-image
rules:
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "next"'
variables: variables:
TAG: "matrix-conduit:next" IMAGE_NAME: $CI_REGISTRY_IMAGE/matrix-conduit
docker:master:
extends: .docker-shared-settings
rules:
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "master"'
variables:
TAG: "matrix-conduit:latest"
docker:tags:
extends: .docker-shared-settings
rules:
- if: "$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_TAG"
variables:
TAG: "matrix-conduit:$CI_COMMIT_TAG"
docker build debugging:
extends: .docker-shared-settings
rules:
- if: "$CI_MERGE_REQUEST_TITLE =~ /.*[Dd]ocker.*/"
variables:
TAG: "matrix-conduit-docker-tests:latest"
# --------------------------------------------------------------------- #
# Run tests #
# --------------------------------------------------------------------- #
cargo check:
stage: test
image: docker.io/rust:1.70.0-bullseye
needs: []
interruptible: true
before_script: before_script:
- "rustup show && rustc --version && cargo --version" # Print version info for debugging - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
script:
- cargo check
oci-image:push-dockerhub:
.test-shared-settings: extends: .push-oci-image
stage: "test"
needs: []
image: "registry.gitlab.com/jfowl/conduit-containers/rust-with-tools:latest"
tags: ["docker"]
variables: variables:
CARGO_INCREMENTAL: "false" # https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow IMAGE_NAME: matrixconduit/matrix-conduit
interruptible: true
test:cargo:
extends: .test-shared-settings
before_script: before_script:
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb - docker login -u $DOCKER_HUB_USER -p $DOCKER_HUB_PASSWORD
script:
- rustc --version && cargo --version # Print version info for debugging
- "cargo test --color always --workspace --verbose --locked --no-fail-fast"
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"

1649
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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,17 +17,17 @@ 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" 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.70.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.6.18", 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 }
@ -26,7 +37,7 @@ tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitiv
# 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 = "3bd58e3c899457c2d55c45268dcb8a65ae682d54", 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/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 = "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 = { 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-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-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
@ -53,7 +64,8 @@ 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.40" thiserror = "1.0.40"
# Used to generate thumbnails for images # Used to generate thumbnails for images
@ -61,13 +73,13 @@ image = { version = "0.24.6", default-features = false, features = ["jpeg", "png
# Used to encode server public key # Used to encode server public key
base64 = "0.21.2" 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.8.1" regex = "1.8.1"
# jwt jsonwebtokens # jwt jsonwebtokens
jsonwebtoken = "8.3.0" jsonwebtoken = "9.2.0"
# Performance measurements # Performance measurements
tracing = { version = "0.1.37", features = [] } tracing = { version = "0.1.37", features = [] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
@ -78,10 +90,10 @@ tracing-opentelemetry = "0.18.0"
lru-cache = "0.1.2" lru-cache = "0.1.2"
rusqlite = { version = "0.29.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.2", optional = true } # crossbeam = { version = "0.8.2", optional = true }
num_cpus = "1.15.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 }
# Used for ruma wrapper # Used for ruma wrapper
serde_html_form = "0.2.0" serde_html_form = "0.2.0"
@ -112,7 +124,7 @@ 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"]

View file

@ -12,11 +12,13 @@ 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 appropriate 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 (e.g. RHEL8), 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.
@ -24,15 +26,19 @@ If you use a system with an older glibc version (e.g. RHEL8), you might need to
[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>
@ -273,7 +279,7 @@ server {
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; proxy_read_timeout 5m;

View file

@ -1,132 +0,0 @@
# syntax=docker/dockerfile:1
FROM docker.io/rust:1.70-bullseye AS base
FROM base 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 base 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 ./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" ]

37
bin/complement Executable file
View 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
View 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

View file

@ -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,16 +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_check_for_updates = 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 && \

View file

@ -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.

10
default.nix Normal file
View 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

View file

@ -76,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.
@ -161,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).

64
engage.toml Normal file
View 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
"""

View file

@ -1,22 +1,41 @@
{ {
"nodes": { "nodes": {
"crane": { "attic": {
"inputs": { "inputs": {
"crane": "crane",
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": [ "flake-utils": "flake-utils",
"flake-utils" "nixpkgs": "nixpkgs",
], "nixpkgs-stable": "nixpkgs-stable"
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1688772518, "lastModified": 1705617092,
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=", "narHash": "sha256-n9PK4O4X4S1JkwpkMuYm1wHZYJzRqif8g3RuVIPD+rY=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "fbe252a5c21febbe920c025560cbd63b20e24f3b",
"type": "github"
},
"original": {
"owner": "zhaofengli",
"ref": "main",
"repo": "attic",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1702918879,
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e", "rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -25,6 +44,27 @@
"type": "github" "type": "github"
} }
}, },
"crane_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1706473964,
"narHash": "sha256-Fq6xleee/TsX6NbtoRuI96bBuDHMU57PrcK9z1QEKbk=",
"owner": "ipetkov",
"repo": "crane",
"rev": "c798790eabec3e3da48190ae3698ac227aab770c",
"type": "github"
},
"original": {
"owner": "ipetkov",
"ref": "master",
"repo": "crane",
"type": "github"
}
},
"fenix": { "fenix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -33,11 +73,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1689488573, "lastModified": 1705559032,
"narHash": "sha256-diVASflKCCryTYv0djvMnP2444mFsIG0ge5pa7ahauQ=", "narHash": "sha256-Cb+Jd1+Gz4Wi+8elPnUIHnqQmE1qjDRZ+PsJaPaAffY=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "39096fe3f379036ff4a5fa198950b8e79defe939", "rev": "e132ea0eb0c799a2109a91688e499d7bf4962801",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -62,16 +102,29 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": { "flake-compat_2": {
"inputs": { "flake": false,
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1689068808, "lastModified": 1696426674,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -80,13 +133,78 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1705332318,
"narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "3449dc925982ad46246cfc36469baf66e1b64f17",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1689444953, "lastModified": 1702539185,
"narHash": "sha256-0o56bfb2LC38wrinPdCGLDScd77LVcr7CrH1zK7qvDg=", "narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8acef304efe70152463a6399f73e636bcc363813", "rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1702780907,
"narHash": "sha256-blbrBBXjjZt6OKTcYX1jpe9SRof2P9ZYWPzq22tzXAA=",
"owner": "NixOS",
"repo": "nixpkgs",
"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": {
@ -98,20 +216,23 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"crane": "crane", "attic": "attic",
"crane": "crane_2",
"fenix": "fenix", "fenix": "fenix",
"flake-utils": "flake-utils", "flake-compat": "flake-compat_2",
"nixpkgs": "nixpkgs" "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": 1689441253, "lastModified": 1705523001,
"narHash": "sha256-4MSDZaFI4DOfsLIZYPMBl0snzWhX1/OqR/QHir382CY=", "narHash": "sha256-TWq5vJ6m+9HGSDMsQAmz1TMegMi79R3TTyKjnPWsQp8=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "996e054f1eb1dbfc8455ecabff0f6ff22ba7f7c8", "rev": "9d9b34354d2f13e33568c9c55b226dd014a146a0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -121,31 +242,6 @@
"type": "github" "type": "github"
} }
}, },
"rust-overlay": {
"inputs": {
"flake-utils": [
"crane",
"flake-utils"
],
"nixpkgs": [
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688351637,
"narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "f9b92316727af9e6c7fee4a761242f7f46880329",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,

266
flake.nix
View file

@ -2,92 +2,258 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
nix-filter.url = "github:numtide/nix-filter";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
fenix = { fenix = {
url = "github:nix-community/fenix"; url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
crane = { crane = {
url = "github:ipetkov/crane"; url = "github:ipetkov/crane?ref=master";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
}; };
attic.url = "github:zhaofengli/attic?ref=main";
}; };
outputs = outputs =
{ self { self
, nixpkgs , nixpkgs
, flake-utils , flake-utils
, nix-filter
, fenix , fenix
, crane , crane
, ...
}: flake-utils.lib.eachDefaultSystem (system: }: flake-utils.lib.eachDefaultSystem (system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgsHost = nixpkgs.legacyPackages.${system};
# Use mold on Linux
stdenv = if pkgs.stdenv.isLinux then
pkgs.stdenvAdapters.useMoldLinker pkgs.stdenv
else
pkgs.stdenv;
# Nix-accessible `Cargo.toml` # Nix-accessible `Cargo.toml`
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
# The Rust toolchain to use # The Rust toolchain to use
toolchain = fenix.packages.${system}.toolchainOf { toolchain = fenix.packages.${system}.fromToolchainFile {
# Use the Rust version defined in `Cargo.toml` file = ./rust-toolchain.toml;
channel = cargoToml.package.rust-version;
# THE rust-version HASH # See also `rust-toolchain.toml`
sha256 = "sha256-gdYqng0y9iHYzYPAdkC/ka3DRny3La/S5G8ASj0Ayyc="; sha256 = "sha256-SXRtAuO4IqNOQq+nLbrsDFbVk+3aVA8NNpSZsKlVH/8=";
}; };
# The system's RocksDB builder = pkgs:
ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb}/include"; ((crane.mkLib pkgs).overrideToolchain toolchain).buildPackage;
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/lib";
# Shared between the package and the devShell nativeBuildInputs = pkgs: [
nativeBuildInputs = (with pkgs.rustPlatform; [ # bindgen needs the build platform's libclang. Apparently due to
bindgenHook # "splicing weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't
]); # quite do the right thing here.
pkgs.buildPackages.rustPlatform.bindgenHook
];
builder = env = pkgs: {
((crane.mkLib pkgs).overrideToolchain toolchain.toolchain).buildPackage; ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb}/include";
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/lib";
}
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isStatic {
ROCKSDB_STATIC = "";
}
// {
CARGO_BUILD_RUSTFLAGS = let inherit (pkgs) lib stdenv; in
lib.concatStringsSep " " ([]
++ lib.optionals
# This disables PIE for static builds, which isn't great in terms
# 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 in
{ {
packages.default = builder { packages = {
src = ./.; 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;
}
inherit # An output for an OCI image based on that binary
stdenv {
nativeBuildInputs name = "oci-image-${crossSystem}";
ROCKSDB_INCLUDE_DIR value = mkOciImage
ROCKSDB_LIB_DIR; pkgsCrossStatic
}; self.packages.${system}.${binaryName};
}
]
)
[
"x86_64-unknown-linux-musl"
"aarch64-unknown-linux-musl"
]
)
);
devShells.default = (pkgs.mkShell.override { inherit stdenv; }) { devShells.default = pkgsHost.mkShell {
# Rust Analyzer needs to be able to find the path to default crate env = env pkgsHost // {
# sources, and it can read this environment variable to do so # Rust Analyzer needs to be able to find the path to default crate
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library"; # sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
inherit RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
ROCKSDB_INCLUDE_DIR };
ROCKSDB_LIB_DIR;
# Development tools # Development tools
nativeBuildInputs = nativeBuildInputs ++ (with toolchain; [ nativeBuildInputs = nativeBuildInputs pkgsHost ++ [
cargo # Always use nightly rustfmt because most of its options are unstable
clippy #
rust-src # This needs to come before `toolchain` in this list, otherwise
rustc # `$PATH` will have stable rustfmt instead.
rustfmt fenix.packages.${system}.latest.rustfmt
]);
};
checks = { toolchain
packagesDefault = self.packages.${system}.default; ] ++ (with pkgsHost; [
devShellsDefault = self.devShells.${system}.default; engage
# Needed for Complement
go
olm
# Needed for our script for Complement
jq
]);
}; };
}); });
} }

22
rust-toolchain.toml Normal file
View 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",
]

View file

@ -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`
/// ///
@ -335,31 +339,68 @@ 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)) => {
for (user, masterkey) in response.master_keys { for (user, masterkey) in response.master_keys {
let (master_key_id, mut master_key) = let (master_key_id, mut master_key) =
services().users.parse_master_key(&user, &masterkey)?; services().users.parse_master_key(&user, &masterkey)?;
@ -386,7 +427,8 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
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!({}));
} }
} }

View file

@ -51,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,
}) })
} }

View file

@ -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,7 +110,12 @@ 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)
} }
@ -400,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,
@ -435,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,
@ -619,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,
@ -630,18 +640,20 @@ 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 { }
warn!( Err(e) => {
"Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}", warn!(
); "Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}: {e:?}",
);
}
} }
} }
@ -710,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
@ -733,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",
@ -1362,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);

View file

@ -124,7 +124,7 @@ pub async fn get_message_events_route(
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,

View file

@ -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",
))
} }
} }

View file

@ -34,6 +34,7 @@ pub async fn redact_event_route(
PduBuilder { PduBuilder {
event_type: TimelineEventType::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"),

View file

@ -23,7 +23,7 @@ pub async fn get_relating_events_with_rel_type_and_event_type_route(
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());
// Use limit or else 10, with maximum 100 // Use limit or else 10, with maximum 100
let limit = body let limit = body
@ -73,7 +73,7 @@ pub async fn get_relating_events_with_rel_type_route(
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());
// Use limit or else 10, with maximum 100 // Use limit or else 10, with maximum 100
let limit = body let limit = body
@ -121,7 +121,7 @@ pub async fn get_relating_events_route(
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());
// Use limit or else 10, with maximum 100 // Use limit or else 10, with maximum 100
let limit = body let limit = body

View file

@ -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(),
) )
@ -365,7 +366,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: TimelineEventType::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()),

View file

@ -42,24 +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); 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)?
@ -105,24 +112,28 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
)); ));
} }
} }
login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService { identifier }) => { login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
identifier,
user,
}) => {
if !body.from_appservice { if !body.from_appservice {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
"Forbidden login type.", "Forbidden login type.",
)); ));
}; };
let username = if let UserIdentifier::UserIdOrLocalpart(user_id) = identifier { 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.")
})?;
user_id
} }
_ => { _ => {
warn!("Unsupported or unknown login type: {:?}", &body.login_info); warn!("Unsupported or unknown login type: {:?}", &body.login_info);
@ -163,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,

View file

@ -85,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,
@ -118,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,
@ -157,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,

View file

@ -20,7 +20,7 @@ use ruma::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
}, },
serde::Raw, serde::Raw,
uint, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId, uint, DeviceId, JsOption, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId,
}; };
use std::{ use std::{
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}, collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
@ -554,6 +554,7 @@ async fn sync_helper(
} }
} }
#[allow(clippy::too_many_arguments)]
async fn load_joined_room( async fn load_joined_room(
sender_user: &UserId, sender_user: &UserId,
sender_device: &DeviceId, sender_device: &DeviceId,
@ -590,7 +591,7 @@ async fn load_joined_room(
|| services() || services()
.rooms .rooms
.user .user
.last_notification_read(&sender_user, &room_id)? .last_notification_read(sender_user, room_id)?
> since; > since;
let mut timeline_users = HashSet::new(); let mut timeline_users = HashSet::new();
@ -599,16 +600,16 @@ async fn load_joined_room(
} }
services().rooms.lazy_loading.lazy_load_confirm_delivery( services().rooms.lazy_loading.lazy_load_confirm_delivery(
&sender_user, sender_user,
&sender_device, sender_device,
&room_id, room_id,
sincecount, sincecount,
)?; )?;
// Database queries: // Database queries:
let current_shortstatehash = let current_shortstatehash =
if let Some(s) = services().rooms.state.get_room_shortstatehash(&room_id)? { if let Some(s) = services().rooms.state.get_room_shortstatehash(room_id)? {
s s
} else { } else {
error!("Room {} has no state", room_id); error!("Room {} has no state", room_id);
@ -618,7 +619,7 @@ async fn load_joined_room(
let since_shortstatehash = services() let since_shortstatehash = services()
.rooms .rooms
.user .user
.get_token_shortstatehash(&room_id, since)?; .get_token_shortstatehash(room_id, since)?;
let (heroes, joined_member_count, invited_member_count, joined_since_last_sync, state_events) = let (heroes, joined_member_count, invited_member_count, joined_since_last_sync, state_events) =
if timeline_pdus.is_empty() && since_shortstatehash == Some(current_shortstatehash) { if timeline_pdus.is_empty() && since_shortstatehash == Some(current_shortstatehash) {
@ -630,12 +631,12 @@ async fn load_joined_room(
let joined_member_count = services() let joined_member_count = services()
.rooms .rooms
.state_cache .state_cache
.room_joined_count(&room_id)? .room_joined_count(room_id)?
.unwrap_or(0); .unwrap_or(0);
let invited_member_count = services() let invited_member_count = services()
.rooms .rooms
.state_cache .state_cache
.room_invited_count(&room_id)? .room_invited_count(room_id)?
.unwrap_or(0); .unwrap_or(0);
// Recalculate heroes (first 5 members) // Recalculate heroes (first 5 members)
@ -648,7 +649,7 @@ async fn load_joined_room(
for hero in services() for hero in services()
.rooms .rooms
.timeline .timeline
.all_pdus(&sender_user, &room_id)? .all_pdus(sender_user, room_id)?
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus .filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
.filter(|(_, pdu)| pdu.kind == TimelineEventType::RoomMember) .filter(|(_, pdu)| pdu.kind == TimelineEventType::RoomMember)
.map(|(_, pdu)| { .map(|(_, pdu)| {
@ -669,11 +670,11 @@ async fn load_joined_room(
) && (services() ) && (services()
.rooms .rooms
.state_cache .state_cache
.is_joined(&user_id, &room_id)? .is_joined(&user_id, room_id)?
|| services() || services()
.rooms .rooms
.state_cache .state_cache
.is_invited(&user_id, &room_id)?) .is_invited(&user_id, room_id)?)
{ {
Ok::<_, Error>(Some(state_key.clone())) Ok::<_, Error>(Some(state_key.clone()))
} else { } else {
@ -789,17 +790,17 @@ async fn load_joined_room(
// Reset lazy loading because this is an initial sync // Reset lazy loading because this is an initial sync
services().rooms.lazy_loading.lazy_load_reset( services().rooms.lazy_loading.lazy_load_reset(
&sender_user, sender_user,
&sender_device, sender_device,
&room_id, room_id,
)?; )?;
// The state_events above should contain all timeline_users, let's mark them as lazy // The state_events above should contain all timeline_users, let's mark them as lazy
// loaded. // loaded.
services().rooms.lazy_loading.lazy_load_mark_sent( services().rooms.lazy_loading.lazy_load_mark_sent(
&sender_user, sender_user,
&sender_device, sender_device,
&room_id, room_id,
lazy_loaded, lazy_loaded,
next_batchcount, next_batchcount,
); );
@ -866,14 +867,14 @@ async fn load_joined_room(
} }
if !services().rooms.lazy_loading.lazy_load_was_sent_before( if !services().rooms.lazy_loading.lazy_load_was_sent_before(
&sender_user, sender_user,
&sender_device, sender_device,
&room_id, room_id,
&event.sender, &event.sender,
)? || lazy_load_send_redundant )? || lazy_load_send_redundant
{ {
if let Some(member_event) = services().rooms.state_accessor.room_state_get( if let Some(member_event) = services().rooms.state_accessor.room_state_get(
&room_id, room_id,
&StateEventType::RoomMember, &StateEventType::RoomMember,
event.sender.as_str(), event.sender.as_str(),
)? { )? {
@ -884,9 +885,9 @@ async fn load_joined_room(
} }
services().rooms.lazy_loading.lazy_load_mark_sent( services().rooms.lazy_loading.lazy_load_mark_sent(
&sender_user, sender_user,
&sender_device, sender_device,
&room_id, room_id,
lazy_loaded, lazy_loaded,
next_batchcount, next_batchcount,
); );
@ -934,7 +935,7 @@ async fn load_joined_room(
match new_membership { match new_membership {
MembershipState::Join => { MembershipState::Join => {
// A new user joined an encrypted room // A new user joined an encrypted room
if !share_encrypted_room(&sender_user, &user_id, &room_id)? { if !share_encrypted_room(sender_user, &user_id, room_id)? {
device_list_updates.insert(user_id); device_list_updates.insert(user_id);
} }
} }
@ -954,15 +955,15 @@ async fn load_joined_room(
services() services()
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(room_id)
.flatten() .flatten()
.filter(|user_id| { .filter(|user_id| {
// Don't send key updates from the sender to the sender // Don't send key updates from the sender to the sender
&sender_user != user_id sender_user != user_id
}) })
.filter(|user_id| { .filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target already // Only send keys if the sender doesn't share an encrypted room with the target already
!share_encrypted_room(&sender_user, user_id, &room_id) !share_encrypted_room(sender_user, user_id, room_id)
.unwrap_or(false) .unwrap_or(false)
}), }),
); );
@ -997,7 +998,7 @@ async fn load_joined_room(
services() services()
.rooms .rooms
.user .user
.notification_count(&sender_user, &room_id)? .notification_count(sender_user, room_id)?
.try_into() .try_into()
.expect("notification count can't go that high"), .expect("notification count can't go that high"),
) )
@ -1010,7 +1011,7 @@ async fn load_joined_room(
services() services()
.rooms .rooms
.user .user
.highlight_count(&sender_user, &room_id)? .highlight_count(sender_user, room_id)?
.try_into() .try_into()
.expect("highlight count can't go that high"), .expect("highlight count can't go that high"),
) )
@ -1039,15 +1040,15 @@ async fn load_joined_room(
.rooms .rooms
.edus .edus
.read_receipt .read_receipt
.readreceipts_since(&room_id, since) .readreceipts_since(room_id, since)
.filter_map(|r| r.ok()) // Filter out buggy events .filter_map(|r| r.ok()) // Filter out buggy events
.map(|(_, _, v)| v) .map(|(_, _, v)| v)
.collect(); .collect();
if services().rooms.edus.typing.last_typing_update(&room_id)? > since { if services().rooms.edus.typing.last_typing_update(room_id)? > since {
edus.push( edus.push(
serde_json::from_str( serde_json::from_str(
&serde_json::to_string(&services().rooms.edus.typing.typings_all(&room_id)?) &serde_json::to_string(&services().rooms.edus.typing.typings_all(room_id)?)
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
) )
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
@ -1056,7 +1057,7 @@ async fn load_joined_room(
// Save the state after this sync so we can send the correct state diff next sync // Save the state after this sync so we can send the correct state diff next sync
services().rooms.user.associate_token_shortstatehash( services().rooms.user.associate_token_shortstatehash(
&room_id, room_id,
next_batch, next_batch,
current_shortstatehash, current_shortstatehash,
)?; )?;
@ -1065,7 +1066,7 @@ async fn load_joined_room(
account_data: RoomAccountData { account_data: RoomAccountData {
events: services() events: services()
.account_data .account_data
.changes_since(Some(&room_id), &sender_user, since)? .changes_since(Some(room_id), sender_user, since)?
.into_iter() .into_iter()
.filter_map(|(_, v)| { .filter_map(|(_, v)| {
serde_json::from_str(v.json().get()) serde_json::from_str(v.json().get())
@ -1102,7 +1103,7 @@ async fn load_joined_room(
fn load_timeline( fn load_timeline(
sender_user: &UserId, sender_user: &UserId,
room_id: &RoomId, room_id: &RoomId,
sincecount: PduCount, roomsincecount: PduCount,
limit: u64, limit: u64,
) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> { ) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> {
let timeline_pdus; let timeline_pdus;
@ -1110,13 +1111,13 @@ fn load_timeline(
if services() if services()
.rooms .rooms
.timeline .timeline
.last_timeline_count(&sender_user, &room_id)? .last_timeline_count(sender_user, room_id)?
> sincecount > roomsincecount
{ {
let mut non_timeline_pdus = services() let mut non_timeline_pdus = services()
.rooms .rooms
.timeline .timeline
.pdus_until(&sender_user, &room_id, PduCount::max())? .pdus_until(sender_user, room_id, PduCount::max())?
.filter_map(|r| { .filter_map(|r| {
// Filter out buggy events // Filter out buggy events
if r.is_err() { if r.is_err() {
@ -1124,7 +1125,7 @@ fn load_timeline(
} }
r.ok() r.ok()
}) })
.take_while(|(pducount, _)| pducount > &sincecount); .take_while(|(pducount, _)| pducount > &roomsincecount);
// Take the last events for the timeline // Take the last events for the timeline
timeline_pdus = non_timeline_pdus timeline_pdus = non_timeline_pdus
@ -1178,16 +1179,15 @@ pub async fn sync_events_v4_route(
// Setup watchers, so if there's no response, we can wait for them // Setup watchers, so if there's no response, we can wait for them
let watcher = services().globals.watch(&sender_user, &sender_device); let watcher = services().globals.watch(&sender_user, &sender_device);
let next_batch = services().globals.current_count()?; let next_batch = services().globals.next_count()?;
let since = body let globalsince = body
.pos .pos
.as_ref() .as_ref()
.and_then(|string| string.parse().ok()) .and_then(|string| string.parse().ok())
.unwrap_or(0); .unwrap_or(0);
let sincecount = PduCount::Normal(since);
if since == 0 { if globalsince == 0 {
if let Some(conn_id) = &body.conn_id { if let Some(conn_id) = &body.conn_id {
services().users.forget_sync_request_connection( services().users.forget_sync_request_connection(
sender_user.clone(), sender_user.clone(),
@ -1214,7 +1214,7 @@ pub async fn sync_events_v4_route(
if body.extensions.to_device.enabled.unwrap_or(false) { if body.extensions.to_device.enabled.unwrap_or(false) {
services() services()
.users .users
.remove_to_device_events(&sender_user, &sender_device, since)?; .remove_to_device_events(&sender_user, &sender_device, globalsince)?;
} }
let mut left_encrypted_users = HashSet::new(); // Users that have left any encrypted rooms the sender was in let mut left_encrypted_users = HashSet::new(); // Users that have left any encrypted rooms the sender was in
@ -1226,13 +1226,13 @@ pub async fn sync_events_v4_route(
device_list_changes.extend( device_list_changes.extend(
services() services()
.users .users
.keys_changed(sender_user.as_ref(), since, None) .keys_changed(sender_user.as_ref(), globalsince, None)
.filter_map(|r| r.ok()), .filter_map(|r| r.ok()),
); );
for room_id in &all_joined_rooms { for room_id in &all_joined_rooms {
let current_shortstatehash = let current_shortstatehash =
if let Some(s) = services().rooms.state.get_room_shortstatehash(&room_id)? { if let Some(s) = services().rooms.state.get_room_shortstatehash(room_id)? {
s s
} else { } else {
error!("Room {} has no state", room_id); error!("Room {} has no state", room_id);
@ -1242,7 +1242,7 @@ pub async fn sync_events_v4_route(
let since_shortstatehash = services() let since_shortstatehash = services()
.rooms .rooms
.user .user
.get_token_shortstatehash(&room_id, since)?; .get_token_shortstatehash(room_id, globalsince)?;
let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash
.and_then(|shortstatehash| { .and_then(|shortstatehash| {
@ -1331,7 +1331,7 @@ pub async fn sync_events_v4_route(
if !share_encrypted_room( if !share_encrypted_room(
&sender_user, &sender_user,
&user_id, &user_id,
&room_id, room_id,
)? { )? {
device_list_changes.insert(user_id); device_list_changes.insert(user_id);
} }
@ -1352,7 +1352,7 @@ pub async fn sync_events_v4_route(
services() services()
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(room_id)
.flatten() .flatten()
.filter(|user_id| { .filter(|user_id| {
// Don't send key updates from the sender to the sender // Don't send key updates from the sender to the sender
@ -1360,7 +1360,7 @@ pub async fn sync_events_v4_route(
}) })
.filter(|user_id| { .filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target already // Only send keys if the sender doesn't share an encrypted room with the target already
!share_encrypted_room(&sender_user, user_id, &room_id) !share_encrypted_room(&sender_user, user_id, room_id)
.unwrap_or(false) .unwrap_or(false)
}), }),
); );
@ -1371,7 +1371,7 @@ pub async fn sync_events_v4_route(
device_list_changes.extend( device_list_changes.extend(
services() services()
.users .users
.keys_changed(room_id.as_ref(), since, None) .keys_changed(room_id.as_ref(), globalsince, None)
.filter_map(|r| r.ok()), .filter_map(|r| r.ok()),
); );
} }
@ -1408,7 +1408,7 @@ pub async fn sync_events_v4_route(
continue; continue;
} }
let mut new_known_rooms = BTreeMap::new(); let mut new_known_rooms = BTreeSet::new();
lists.insert( lists.insert(
list_id.clone(), list_id.clone(),
@ -1424,12 +1424,12 @@ pub async fn sync_events_v4_route(
let room_ids = all_joined_rooms let room_ids = all_joined_rooms
[(u64::from(r.0) as usize)..=(u64::from(r.1) as usize)] [(u64::from(r.0) as usize)..=(u64::from(r.1) as usize)]
.to_vec(); .to_vec();
new_known_rooms.extend(room_ids.iter().cloned().map(|r| (r, true))); new_known_rooms.extend(room_ids.iter().cloned());
for room_id in &room_ids { for room_id in &room_ids {
let todo_room = todo_rooms.entry(room_id.clone()).or_insert(( let todo_room = todo_rooms.entry(room_id.clone()).or_insert((
BTreeSet::new(), BTreeSet::new(),
0, 0,
true, u64::MAX,
)); ));
let limit = list let limit = list
.room_details .room_details
@ -1440,14 +1440,18 @@ pub async fn sync_events_v4_route(
.0 .0
.extend(list.room_details.required_state.iter().cloned()); .extend(list.room_details.required_state.iter().cloned());
todo_room.1 = todo_room.1.max(limit); todo_room.1 = todo_room.1.max(limit);
if known_rooms.get(&list_id).and_then(|k| k.get(room_id)) != Some(&true) // 0 means unknown because it got out of date
{ todo_room.2 = todo_room.2.min(
todo_room.2 = false; known_rooms
} .get(&list_id)
.and_then(|k| k.get(room_id))
.copied()
.unwrap_or(0),
);
} }
sync_events::v4::SyncOp { sync_events::v4::SyncOp {
op: SlidingOp::Sync, op: SlidingOp::Sync,
range: Some(r.clone()), range: Some(r),
index: None, index: None,
room_ids, room_ids,
room_id: None, room_id: None,
@ -1465,26 +1469,31 @@ pub async fn sync_events_v4_route(
conn_id.clone(), conn_id.clone(),
list_id, list_id,
new_known_rooms, new_known_rooms,
globalsince,
); );
} }
} }
let mut known_subscription_rooms = BTreeMap::new(); let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions { for (room_id, room) in &body.room_subscriptions {
if !services().rooms.metadata.exists(room_id)? {
continue;
}
let todo_room = todo_rooms let todo_room = todo_rooms
.entry(room_id.clone()) .entry(room_id.clone())
.or_insert((BTreeSet::new(), 0, true)); .or_insert((BTreeSet::new(), 0, u64::MAX));
let limit = room.timeline_limit.map_or(10, u64::from).min(100); let limit = room.timeline_limit.map_or(10, u64::from).min(100);
todo_room.0.extend(room.required_state.iter().cloned()); todo_room.0.extend(room.required_state.iter().cloned());
todo_room.1 = todo_room.1.max(limit); todo_room.1 = todo_room.1.max(limit);
if known_rooms // 0 means unknown because it got out of date
.get("subscriptions") todo_room.2 = todo_room.2.min(
.and_then(|k| k.get(room_id)) known_rooms
!= Some(&true) .get("subscriptions")
{ .and_then(|k| k.get(room_id))
todo_room.2 = false; .copied()
} .unwrap_or(0),
known_subscription_rooms.insert(room_id.clone(), true); );
known_subscription_rooms.insert(room_id.clone());
} }
for r in body.unsubscribe_rooms { for r in body.unsubscribe_rooms {
@ -1499,6 +1508,7 @@ pub async fn sync_events_v4_route(
conn_id.clone(), conn_id.clone(),
"subscriptions".to_owned(), "subscriptions".to_owned(),
known_subscription_rooms, known_subscription_rooms,
globalsince,
); );
} }
@ -1512,12 +1522,13 @@ pub async fn sync_events_v4_route(
} }
let mut rooms = BTreeMap::new(); let mut rooms = BTreeMap::new();
for (room_id, (required_state_request, timeline_limit, known)) in &todo_rooms { for (room_id, (required_state_request, timeline_limit, roomsince)) in &todo_rooms {
// TODO: per-room sync tokens let roomsincecount = PduCount::Normal(*roomsince);
let (timeline_pdus, limited) =
load_timeline(&sender_user, &room_id, sincecount, *timeline_limit)?;
if *known && timeline_pdus.is_empty() { let (timeline_pdus, limited) =
load_timeline(&sender_user, room_id, roomsincecount, *timeline_limit)?;
if roomsince != &0 && timeline_pdus.is_empty() {
continue; continue;
} }
@ -1533,8 +1544,8 @@ pub async fn sync_events_v4_route(
})) }))
})? })?
.or_else(|| { .or_else(|| {
if since != 0 { if roomsince != &0 {
Some(since.to_string()) Some(roomsince.to_string())
} else { } else {
None None
} }
@ -1547,63 +1558,58 @@ pub async fn sync_events_v4_route(
let required_state = required_state_request let required_state = required_state_request
.iter() .iter()
.map(|state| { .flat_map(|state| {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &state.0, &state.1) .room_state_get(room_id, &state.0, &state.1)
.ok()
.flatten()
.map(|state| state.to_sync_state_event())
}) })
.filter_map(|r| r.ok())
.filter_map(|o| o)
.map(|state| state.to_sync_state_event())
.collect(); .collect();
// Heroes // Heroes
let heroes = services() let heroes = services()
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(room_id)
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.filter(|member| member != &sender_user) .filter(|member| member != &sender_user)
.map(|member| { .flat_map(|member| {
Ok::<_, Error>( services()
services() .rooms
.rooms .state_accessor
.state_accessor .get_member(room_id, &member)
.get_member(&room_id, &member)? .ok()
.map(|memberevent| { .flatten()
( .map(|memberevent| {
memberevent (
.displayname memberevent
.unwrap_or_else(|| member.to_string()), .displayname
memberevent.avatar_url, .unwrap_or_else(|| member.to_string()),
) memberevent.avatar_url,
}), )
) })
}) })
.filter_map(|r| r.ok())
.filter_map(|o| o)
.take(5) .take(5)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let name = if heroes.len() > 1 { let name = match &heroes[..] {
let last = heroes[0].0.clone(); [] => None,
Some( [only] => Some(only.0.clone()),
heroes[1..] [firsts @ .., last] => Some(
firsts
.iter() .iter()
.map(|h| h.0.clone()) .map(|h| h.0.clone())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
+ " and " + " and "
+ &last, + &last.0,
) ),
} else if heroes.len() == 1 {
Some(heroes[0].0.clone())
} else {
None
}; };
let avatar = if heroes.len() == 1 { let avatar = if let [only] = &heroes[..] {
heroes[0].1.clone() only.1.clone()
} else { } else {
None None
}; };
@ -1611,17 +1617,17 @@ pub async fn sync_events_v4_route(
rooms.insert( rooms.insert(
room_id.clone(), room_id.clone(),
sync_events::v4::SlidingSyncRoom { sync_events::v4::SlidingSyncRoom {
name: services() name: services().rooms.state_accessor.get_name(room_id)?.or(name),
.rooms avatar: if let Some(avatar) = avatar {
.state_accessor JsOption::Some(avatar)
.get_name(&room_id)? } else {
.or_else(|| name), match services().rooms.state_accessor.get_avatar(room_id)? {
avatar: services() JsOption::Some(avatar) => JsOption::from_option(avatar.url),
.rooms JsOption::Null => JsOption::Null,
.state_accessor JsOption::Undefined => JsOption::Undefined,
.get_avatar(&room_id)? }
.map_or(avatar, |a| a.url), },
initial: Some(!known), initial: Some(roomsince == &0),
is_dm: None, is_dm: None,
invite_state: None, invite_state: None,
unread_notifications: UnreadNotificationsCount { unread_notifications: UnreadNotificationsCount {
@ -1629,7 +1635,7 @@ pub async fn sync_events_v4_route(
services() services()
.rooms .rooms
.user .user
.highlight_count(&sender_user, &room_id)? .highlight_count(&sender_user, room_id)?
.try_into() .try_into()
.expect("notification count can't go that high"), .expect("notification count can't go that high"),
), ),
@ -1637,7 +1643,7 @@ pub async fn sync_events_v4_route(
services() services()
.rooms .rooms
.user .user
.notification_count(&sender_user, &room_id)? .notification_count(&sender_user, room_id)?
.try_into() .try_into()
.expect("notification count can't go that high"), .expect("notification count can't go that high"),
), ),
@ -1650,7 +1656,7 @@ pub async fn sync_events_v4_route(
(services() (services()
.rooms .rooms
.state_cache .state_cache
.room_joined_count(&room_id)? .room_joined_count(room_id)?
.unwrap_or(0) as u32) .unwrap_or(0) as u32)
.into(), .into(),
), ),
@ -1658,11 +1664,12 @@ pub async fn sync_events_v4_route(
(services() (services()
.rooms .rooms
.state_cache .state_cache
.room_invited_count(&room_id)? .room_invited_count(room_id)?
.unwrap_or(0) as u32) .unwrap_or(0) as u32)
.into(), .into(),
), ),
num_live: None, // Count events in timeline greater than global sync counter num_live: None, // Count events in timeline greater than global sync counter
timestamp: None,
}, },
); );
} }
@ -1681,7 +1688,7 @@ pub async fn sync_events_v4_route(
} }
Ok(sync_events::v4::Response { Ok(sync_events::v4::Response {
initial: since == 0, initial: globalsince == 0,
txn_id: body.txn_id.clone(), txn_id: body.txn_id.clone(),
pos: next_batch.to_string(), pos: next_batch.to_string(),
lists, lists,
@ -1712,7 +1719,7 @@ pub async fn sync_events_v4_route(
global: if body.extensions.account_data.enabled.unwrap_or(false) { global: if body.extensions.account_data.enabled.unwrap_or(false) {
services() services()
.account_data .account_data
.changes_since(None, &sender_user, since)? .changes_since(None, &sender_user, globalsince)?
.into_iter() .into_iter()
.filter_map(|(_, v)| { .filter_map(|(_, v)| {
serde_json::from_str(v.json().get()) serde_json::from_str(v.json().get())

View file

@ -26,6 +26,7 @@ pub async fn get_supported_versions_route(
"v1.2".to_owned(), "v1.2".to_owned(),
"v1.3".to_owned(), "v1.3".to_owned(),
"v1.4".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)]),
}; };

View file

@ -341,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}");
@ -666,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
@ -707,7 +707,24 @@ 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) => {
@ -718,11 +735,6 @@ pub async fn send_transaction_message_route(
}; };
// 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
@ -980,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(
@ -1046,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
@ -1063,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 {
@ -1787,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()
@ -1861,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;
@ -1897,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()
}) })
@ -1919,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 {

View file

@ -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 {

View file

@ -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()
}), }),
) )

View file

@ -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) });
} }
} }

View file

@ -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<()>)>>,
} }

View file

@ -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.")
})?, })?,

View file

@ -256,8 +256,8 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
.. ..
} = 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(),

View file

@ -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.")

View file

@ -20,7 +20,7 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
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;
@ -49,7 +49,7 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
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(
( (
@ -101,7 +101,7 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
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)
})) }))

View file

@ -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,

View file

@ -10,7 +10,7 @@ impl service::rooms::threads::Data for KeyValueDatabase {
user_id: &'a UserId, user_id: &'a UserId,
room_id: &'a RoomId, room_id: &'a RoomId,
until: u64, until: u64,
include: &'a IncludeThreads, _include: &'a IncludeThreads,
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> { ) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> {
let prefix = services() let prefix = services()
.rooms .rooms
@ -27,7 +27,7 @@ impl service::rooms::threads::Data for KeyValueDatabase {
self.threadid_userids self.threadid_userids
.iter_from(&current, true) .iter_from(&current, true)
.take_while(move |(k, _)| k.starts_with(&prefix)) .take_while(move |(k, _)| k.starts_with(&prefix))
.map(move |(pduid, users)| { .map(move |(pduid, _users)| {
let count = utils::u64_from_bytes(&pduid[(mem::size_of::<u64>())..]) let count = utils::u64_from_bytes(&pduid[(mem::size_of::<u64>())..])
.map_err(|_| Error::bad_database("Invalid pduid in threadid_userids."))?; .map_err(|_| Error::bad_database("Invalid pduid in threadid_userids."))?;
let mut pdu = services() let mut pdu = services()
@ -52,13 +52,13 @@ impl service::rooms::threads::Data for KeyValueDatabase {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(&[0xff][..]); .join(&[0xff][..]);
self.threadid_userids.insert(&root_id, &users)?; self.threadid_userids.insert(root_id, &users)?;
Ok(()) Ok(())
} }
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>> { fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>> {
if let Some(users) = self.threadid_userids.get(&root_id)? { if let Some(users) = self.threadid_userids.get(root_id)? {
Ok(Some( Ok(Some(
users users
.split(|b| *b == 0xff) .split(|b| *b == 0xff)

View file

@ -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())?
@ -232,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();
@ -259,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();
@ -334,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();

View file

@ -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()
} }

View file

@ -852,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);
} }
} }
@ -875,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);
} }
} }

View file

@ -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;

View file

@ -1,13 +1,3 @@
#![warn(
rust_2018_idioms,
unused_qualifications,
clippy::cloned_instead_of_copied,
clippy::str_to_string,
clippy::future_not_send
)]
#![allow(clippy::suspicious_else_formatting)]
#![deny(clippy::dbg_macro)]
use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration}; use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
use axum::{ use axum::{
@ -238,7 +228,7 @@ async fn spawn_task<B: Send + 'static>(
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
} }
async fn unrecognized_method<B>( 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> {

View file

@ -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
/// # ``` /// # ```
@ -165,14 +165,14 @@ enum AdminCommand {
EnableRoom { room_id: Box<RoomId> }, EnableRoom { room_id: Box<RoomId> },
/// Verify json signatures /// Verify json signatures
/// [commandbody] /// [commandbody]()
/// # ``` /// # ```
/// # json here /// # json here
/// # ``` /// # ```
SignJson, SignJson,
/// Verify json signatures /// Verify json signatures
/// [commandbody] /// [commandbody]()
/// # ``` /// # ```
/// # json here /// # json here
/// # ``` /// # ```
@ -858,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
@ -932,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();
@ -1048,7 +1051,7 @@ impl Service {
services().rooms.timeline.build_and_append_pdu( services().rooms.timeline.build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: TimelineEventType::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()),

View file

@ -8,6 +8,12 @@ use ruma::{
use crate::api::server_server::FedDest; use crate::api::server_server::FedDest;
use crate::{services, 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,
@ -17,8 +23,10 @@ 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::{ sync::{
@ -56,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<()>>>>,
@ -98,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();
@ -119,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
@ -160,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()),

View file

@ -385,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 {

View file

@ -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;
@ -193,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
@ -201,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))

View file

@ -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,

View file

@ -92,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()));
@ -117,7 +119,15 @@ 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)?; self.check_room_id(room_id, &incoming_pdu)?;
@ -174,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) {
@ -226,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,
@ -266,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,
@ -274,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 {
@ -315,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(
@ -323,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,
}; };
@ -341,23 +378,25 @@ impl Service {
self.check_room_id(room_id, &incoming_pdu)?; self.check_room_id(room_id, &incoming_pdu)?;
// 4. fetch any missing auth events doing all checks listed here starting at 1. These are not timeline events if !auth_events_known {
// 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" // 4. fetch any missing auth events doing all checks listed here starting at 1. These are not timeline events
// NOTE: Step 5 is not applied anymore because it failed too often // 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"
debug!(event_id = ?incoming_pdu.event_id, "Fetching auth events"); // NOTE: Step 5 is not applied anymore because it failed too often
self.fetch_and_handle_outliers( debug!(event_id = ?incoming_pdu.event_id, "Fetching auth events");
origin, self.fetch_and_handle_outliers(
&incoming_pdu origin,
.auth_events &incoming_pdu
.iter() .auth_events
.map(|x| Arc::from(&**x)) .iter()
.collect::<Vec<_>>(), .map(|x| Arc::from(&**x))
create_event, .collect::<Vec<_>>(),
room_id, create_event,
room_version_id, room_id,
pub_key_map, room_version_id,
) 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
debug!( debug!(
@ -942,14 +981,21 @@ impl Service {
debug!("Resolving state"); debug!("Resolving state");
let lock = services().globals.stateres_mutex.lock(); let fetch_event = |id: &_| {
let state = match state_res::resolve(room_version_id, &fork_states, auth_chain_sets, |id| {
let res = services().rooms.timeline.get_pdu(id); let res = services().rooms.timeline.get_pdu(id);
if let Err(e) = &res { if let Err(e) = &res {
error!("LOOK AT ME Failed to fetch event: {}", e); error!("LOOK AT ME Failed to fetch event: {}", e);
} }
res.ok().flatten() res.ok().flatten()
}) { };
let lock = services().globals.stateres_mutex.lock();
let state = match state_res::resolve(
room_version_id,
&fork_states,
auth_chain_sets,
fetch_event,
) {
Ok(new_state) => new_state, Ok(new_state) => new_state,
Err(_) => { Err(_) => {
return Err(Error::bad_database("State resolution failed, either an event could not be found or deserialization")); return Err(Error::bad_database("State resolution failed, either an event could not be found or deserialization"));
@ -986,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,
@ -1013,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)
@ -1050,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;
} }
@ -1060,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;
} }
@ -1119,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,
@ -1126,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
@ -1530,6 +1598,11 @@ 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 {

View file

@ -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>>>,
} }

View file

@ -5,6 +5,7 @@ use ruma::{EventId, RoomId, UserId};
pub trait Data: Send + Sync { pub trait Data: Send + Sync {
fn add_relation(&self, from: u64, to: u64) -> Result<()>; fn add_relation(&self, from: u64, to: u64) -> Result<()>;
#[allow(clippy::type_complexity)]
fn relations_until<'a>( fn relations_until<'a>(
&'a self, &'a self,
user_id: &'a UserId, user_id: &'a UserId,

View file

@ -40,6 +40,7 @@ impl Service {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn paginate_relations_with_filter( pub fn paginate_relations_with_filter(
&self, &self,
sender_user: &UserId, sender_user: &UserId,
@ -82,7 +83,7 @@ impl Service {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id) .user_can_see_event(sender_user, room_id, &pdu.event_id)
.unwrap_or(false) .unwrap_or(false)
}) })
.take_while(|&(k, _)| Some(k) != to) // Stop at `to` .take_while(|&(k, _)| Some(k) != to) // Stop at `to`
@ -106,7 +107,7 @@ impl Service {
let events_before: Vec<_> = services() let events_before: Vec<_> = services()
.rooms .rooms
.pdu_metadata .pdu_metadata
.relations_until(sender_user, &room_id, target, from)? .relations_until(sender_user, room_id, target, from)?
.filter(|r| { .filter(|r| {
r.as_ref().map_or(true, |(_, pdu)| { r.as_ref().map_or(true, |(_, pdu)| {
filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t) filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t)
@ -129,7 +130,7 @@ impl Service {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id) .user_can_see_event(sender_user, room_id, &pdu.event_id)
.unwrap_or(false) .unwrap_or(false)
}) })
.take_while(|&(k, _)| Some(k) != to) // Stop at `to` .take_while(|&(k, _)| Some(k) != to) // Stop at `to`

View file

@ -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,

View file

@ -134,7 +134,7 @@ impl Service {
if serde_json::from_str::<SpaceChildEventContent>(pdu.content.get()) if serde_json::from_str::<SpaceChildEventContent>(pdu.content.get())
.ok() .ok()
.and_then(|c| c.via) .map(|c| c.via)
.map_or(true, |v| v.is_empty()) .map_or(true, |v| v.is_empty())
{ {
continue; continue;
@ -185,7 +185,9 @@ impl Service {
stack.push(children_ids); stack.push(children_ids);
} }
} else { } else {
let server = current_room.server_name(); let server = current_room
.server_name()
.expect("Room IDs should always have a server name");
if server == services().globals.server_name() { if server == services().globals.server_name() {
continue; continue;
} }
@ -193,11 +195,11 @@ impl Service {
// Early return so the client can see some data already // Early return so the client can see some data already
break; break;
} }
warn!("Asking {server} for /hierarchy"); debug!("Asking {server} for /hierarchy");
if let Ok(response) = services() if let Ok(response) = services()
.sending .sending
.send_federation_request( .send_federation_request(
&server, server,
federation::space::get_hierarchy::v1::Request { federation::space::get_hierarchy::v1::Request {
room_id: current_room.to_owned(), room_id: current_room.to_owned(),
suggested_only, suggested_only,
@ -235,7 +237,7 @@ impl Service {
.room .room
.allowed_room_ids .allowed_room_ids
.into_iter() .into_iter()
.map(|room| AllowRule::room_membership(room)) .map(AllowRule::room_membership)
.collect(), .collect(),
}) })
} }
@ -245,7 +247,7 @@ impl Service {
.room .room
.allowed_room_ids .allowed_room_ids
.into_iter() .into_iter()
.map(|room| AllowRule::room_membership(room)) .map(AllowRule::room_membership)
.collect(), .collect(),
}) })
} }
@ -313,7 +315,7 @@ impl Service {
canonical_alias: services() canonical_alias: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")? .room_state_get(room_id, &StateEventType::RoomCanonicalAlias, "")?
.map_or(Ok(None), |s| { .map_or(Ok(None), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomCanonicalAliasEventContent| c.alias) .map(|c: RoomCanonicalAliasEventContent| c.alias)
@ -321,11 +323,11 @@ impl Service {
Error::bad_database("Invalid canonical alias event in database.") Error::bad_database("Invalid canonical alias event in database.")
}) })
})?, })?,
name: services().rooms.state_accessor.get_name(&room_id)?, name: services().rooms.state_accessor.get_name(room_id)?,
num_joined_members: services() num_joined_members: services()
.rooms .rooms
.state_cache .state_cache
.room_joined_count(&room_id)? .room_joined_count(room_id)?
.unwrap_or_else(|| { .unwrap_or_else(|| {
warn!("Room {} has no member count", room_id); warn!("Room {} has no member count", room_id);
0 0
@ -336,7 +338,7 @@ impl Service {
topic: services() topic: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomTopic, "")? .room_state_get(room_id, &StateEventType::RoomTopic, "")?
.map_or(Ok(None), |s| { .map_or(Ok(None), |s| {
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))
@ -348,7 +350,7 @@ impl Service {
world_readable: services() world_readable: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")? .room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
.map_or(Ok(false), |s| { .map_or(Ok(false), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomHistoryVisibilityEventContent| { .map(|c: RoomHistoryVisibilityEventContent| {
@ -363,7 +365,7 @@ impl Service {
guest_can_join: services() guest_can_join: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")? .room_state_get(room_id, &StateEventType::RoomGuestAccess, "")?
.map_or(Ok(false), |s| { .map_or(Ok(false), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomGuestAccessEventContent| { .map(|c: RoomGuestAccessEventContent| {
@ -376,7 +378,7 @@ impl Service {
avatar_url: services() avatar_url: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")? .room_state_get(room_id, &StateEventType::RoomAvatar, "")?
.map(|s| { .map(|s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomAvatarEventContent| c.url) .map(|c: RoomAvatarEventContent| c.url)
@ -389,7 +391,7 @@ impl Service {
let join_rule = services() let join_rule = services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")? .room_state_get(room_id, &StateEventType::RoomJoinRules, "")?
.map(|s| { .map(|s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomJoinRulesEventContent| c.join_rule) .map(|c: RoomJoinRulesEventContent| c.join_rule)
@ -415,7 +417,7 @@ impl Service {
room_type: services() room_type: services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")? .room_state_get(room_id, &StateEventType::RoomCreate, "")?
.map(|s| { .map(|s| {
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| { serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
error!("Invalid room create event in database: {}", e); error!("Invalid room create event in database: {}", e);
@ -455,7 +457,7 @@ impl Service {
SpaceRoomJoinRule::Invite => services() SpaceRoomJoinRule::Invite => services()
.rooms .rooms
.state_cache .state_cache
.is_joined(sender_user, &room_id)?, .is_joined(sender_user, room_id)?,
_ => false, _ => false,
}; };
@ -479,17 +481,14 @@ impl Service {
match join_rule { match join_rule {
JoinRule::Restricted(r) => { JoinRule::Restricted(r) => {
for rule in &r.allow { for rule in &r.allow {
match rule { if let join_rules::AllowRule::RoomMembership(rm) = rule {
join_rules::AllowRule::RoomMembership(rm) => { if let Ok(true) = services()
if let Ok(true) = services() .rooms
.rooms .state_cache
.state_cache .is_joined(sender_user, &rm.room_id)
.is_joined(sender_user, &rm.room_id) {
{ return Ok(true);
return Ok(true);
}
} }
_ => {}
} }
} }

View file

@ -6,6 +6,7 @@ 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, StateEventType, TimelineEventType, AnyStrippedStateEvent, StateEventType, TimelineEventType,
@ -40,7 +41,7 @@ impl Service {
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)
}) { }) {
@ -331,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| {
@ -339,14 +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_else(|| { Ok(create_event_content.room_version)
warn!("Invalid room version for room {room_id}");
Error::BadDatabase("Invalid 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>> {
@ -415,7 +412,7 @@ impl Service {
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)| {

View file

@ -16,7 +16,7 @@ use ruma::{
}, },
StateEventType, StateEventType,
}, },
EventId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
}; };
use tracing::error; use tracing::error;
@ -180,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, "")?
@ -197,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}");
@ -221,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)
@ -276,20 +276,26 @@ impl Service {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomName, "")? .room_state_get(room_id, &StateEventType::RoomName, "")?
.map_or(Ok(None), |s| { .map_or(Ok(None), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map(|c: RoomNameEventContent| c.name) .map(|c: RoomNameEventContent| Some(c.name))
.map_err(|_| Error::bad_database("Invalid room name event in database.")) .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<Option<RoomAvatarEventContent>> { pub fn get_avatar(&self, room_id: &RoomId) -> Result<JsOption<RoomAvatarEventContent>> {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")? .room_state_get(room_id, &StateEventType::RoomAvatar, "")?
.map_or(Ok(None), |s| { .map_or(Ok(JsOption::Undefined), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map_err(|_| Error::bad_database("Invalid room avatar event in database.")) .map_err(|_| Error::bad_database("Invalid room avatar event in database."))
}) })
@ -303,7 +309,7 @@ impl Service {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomMember, user_id.as_str())? .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
.map_or(Ok(None), |s| { .map_or(Ok(None), |s| {
serde_json::from_str(s.content.get()) serde_json::from_str(s.content.get())
.map_err(|_| Error::bad_database("Invalid room member event in database.")) .map_err(|_| Error::bad_database("Invalid room member event in database."))

View file

@ -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,

View file

@ -16,6 +16,7 @@ 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,
@ -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,
@ -131,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,
@ -164,7 +167,7 @@ impl Service {
for removed in statediffremoved.iter() { 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.clone()); 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
} }
@ -172,7 +175,7 @@ impl Service {
for new in statediffnew.iter() { 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.clone()); 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
} }
@ -217,7 +220,7 @@ impl Service {
for removed in statediffremoved.iter() { 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.clone()); 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
} }
@ -225,7 +228,7 @@ impl Service {
for new in statediffnew.iter() { 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.clone()); 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
} }
@ -253,6 +256,7 @@ 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,

View file

@ -2,6 +2,7 @@ use crate::{PduEvent, Result};
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId}; use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
pub trait Data: Send + Sync { pub trait Data: Send + Sync {
#[allow(clippy::type_complexity)]
fn threads_until<'a>( fn threads_until<'a>(
&'a self, &'a self,
user_id: &'a UserId, user_id: &'a UserId,

View file

@ -26,7 +26,7 @@ impl Service {
self.db.threads_until(user_id, room_id, until, include) self.db.threads_until(user_id, room_id, until, include)
} }
pub fn add_to_thread<'a>(&'a self, root_event_id: &EventId, pdu: &PduEvent) -> Result<()> { pub fn add_to_thread(&self, root_event_id: &EventId, pdu: &PduEvent) -> Result<()> {
let root_id = &services() let root_id = &services()
.rooms .rooms
.timeline .timeline
@ -103,7 +103,7 @@ impl Service {
} }
let mut users = Vec::new(); let mut users = Vec::new();
if let Some(userids) = self.db.get_participants(&root_id)? { if let Some(userids) = self.db.get_participants(root_id)? {
users.extend_from_slice(&userids); users.extend_from_slice(&userids);
users.push(pdu.sender.clone()); users.push(pdu.sender.clone());
} else { } else {

View file

@ -66,6 +66,7 @@ pub trait Data: Send + Sync {
/// 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,
@ -75,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,

View file

@ -58,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)
} }
@ -90,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,
@ -112,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()
@ -320,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;
@ -458,7 +459,7 @@ impl Service {
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.starts_with(&format!("{server_user} "))
|| body == format!("{server_user}:") || body == 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
@ -842,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);
@ -850,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,
@ -876,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,
@ -1048,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")?;
@ -1060,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"))
@ -1091,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}");
} }
@ -1142,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");
@ -1175,24 +1174,21 @@ impl Service {
drop(insert_lock); drop(insert_lock);
match pdu.kind { if pdu.kind == TimelineEventType::RoomMessage {
TimelineEventType::RoomMessage => { #[derive(Deserialize)]
#[derive(Deserialize)] struct ExtractBody {
struct ExtractBody { body: Option<String>,
body: Option<String>, }
}
let content = serde_json::from_str::<ExtractBody>(pdu.content.get())
let content = serde_json::from_str::<ExtractBody>(pdu.content.get()) .map_err(|_| Error::bad_database("Invalid content in pdu."))?;
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
if let Some(body) = content.body {
if let Some(body) = content.body { services()
services() .rooms
.rooms .search
.search .index_pdu(shortroomid, &pdu_id, &body)?;
.index_pdu(shortroomid, &pdu_id, &body)?;
}
} }
_ => {}
} }
drop(mutex_lock); drop(mutex_lock);
@ -1200,3 +1196,16 @@ impl Service {
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));
}
}

View file

@ -5,6 +5,7 @@ use crate::Result;
use super::{OutgoingKind, SendingEventType}; use super::{OutgoingKind, SendingEventType};
pub trait Data: Send + Sync { pub trait Data: Send + Sync {
#[allow(clippy::type_complexity)]
fn active_requests<'a>( fn active_requests<'a>(
&'a self, &'a self,
) -> Box<dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>> + 'a>; ) -> Box<dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>> + 'a>;

View file

@ -131,7 +131,7 @@ impl Service {
for (key, outgoing_kind, event) in self.db.active_requests().filter_map(|r| r.ok()) { for (key, outgoing_kind, event) in self.db.active_requests().filter_map(|r| r.ok()) {
let entry = initial_transactions let entry = initial_transactions
.entry(outgoing_kind.clone()) .entry(outgoing_kind.clone())
.or_insert_with(Vec::new); .or_default();
if entry.len() > 30 { if entry.len() > 30 {
warn!( warn!(

View file

@ -1,6 +1,6 @@
mod data; mod data;
use std::{ use std::{
collections::BTreeMap, collections::{BTreeMap, BTreeSet},
mem, mem,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@ -28,12 +28,13 @@ use crate::{services, Error, Result};
pub struct SlidingSyncCache { pub struct SlidingSyncCache {
lists: BTreeMap<String, SyncRequestList>, lists: BTreeMap<String, SyncRequestList>,
subscriptions: BTreeMap<OwnedRoomId, sync_events::v4::RoomSubscription>, subscriptions: BTreeMap<OwnedRoomId, sync_events::v4::RoomSubscription>,
known_rooms: BTreeMap<String, BTreeMap<OwnedRoomId, bool>>, known_rooms: BTreeMap<String, BTreeMap<OwnedRoomId, u64>>, // For every room, the roomsince number
extensions: ExtensionsConfig, extensions: ExtensionsConfig,
} }
pub struct Service { pub struct Service {
pub db: &'static dyn Data, pub db: &'static dyn Data,
#[allow(clippy::type_complexity)]
pub connections: pub connections:
Mutex<BTreeMap<(OwnedUserId, OwnedDeviceId, String), Arc<Mutex<SlidingSyncCache>>>>, Mutex<BTreeMap<(OwnedUserId, OwnedDeviceId, String), Arc<Mutex<SlidingSyncCache>>>>,
} }
@ -61,7 +62,7 @@ impl Service {
user_id: OwnedUserId, user_id: OwnedUserId,
device_id: OwnedDeviceId, device_id: OwnedDeviceId,
request: &mut sync_events::v4::Request, request: &mut sync_events::v4::Request,
) -> BTreeMap<String, BTreeMap<OwnedRoomId, bool>> { ) -> BTreeMap<String, BTreeMap<OwnedRoomId, u64>> {
let Some(conn_id) = request.conn_id.clone() else { let Some(conn_id) = request.conn_id.clone() else {
return BTreeMap::new(); return BTreeMap::new();
}; };
@ -127,6 +128,7 @@ impl Service {
} }
} }
(_, Some(cached_filters)) => list.filters = Some(cached_filters), (_, Some(cached_filters)) => list.filters = Some(cached_filters),
(Some(list_filters), _) => list.filters = Some(list_filters.clone()),
(_, _) => {} (_, _) => {}
} }
if list.bump_event_types.is_empty() { if list.bump_event_types.is_empty() {
@ -136,12 +138,18 @@ impl Service {
cached.lists.insert(list_id.clone(), list.clone()); cached.lists.insert(list_id.clone(), list.clone());
} }
cached cached.subscriptions.extend(
.subscriptions request
.extend(request.room_subscriptions.clone().into_iter()); .room_subscriptions
request .iter()
.room_subscriptions .map(|(k, v)| (k.clone(), v.clone())),
.extend(cached.subscriptions.clone().into_iter()); );
request.room_subscriptions.extend(
cached
.subscriptions
.iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
request.extensions.e2ee.enabled = request request.extensions.e2ee.enabled = request
.extensions .extensions
@ -210,7 +218,8 @@ impl Service {
device_id: OwnedDeviceId, device_id: OwnedDeviceId,
conn_id: String, conn_id: String,
list_id: String, list_id: String,
new_cached_rooms: BTreeMap<OwnedRoomId, bool>, new_cached_rooms: BTreeSet<OwnedRoomId>,
globalsince: u64,
) { ) {
let mut cache = self.connections.lock().unwrap(); let mut cache = self.connections.lock().unwrap();
let cached = Arc::clone( let cached = Arc::clone(
@ -228,7 +237,20 @@ impl Service {
let cached = &mut cached.lock().unwrap(); let cached = &mut cached.lock().unwrap();
drop(cache); drop(cache);
cached.known_rooms.insert(list_id, new_cached_rooms); for (roomid, lastsince) in cached
.known_rooms
.entry(list_id.clone())
.or_default()
.iter_mut()
{
if !new_cached_rooms.contains(roomid) {
*lastsince = 0;
}
}
let list = cached.known_rooms.entry(list_id).or_default();
for roomid in new_cached_rooms {
list.insert(roomid, globalsince);
}
} }
/// Check if account is deactivated /// Check if account is deactivated

View file

@ -116,9 +116,11 @@ impl Error {
Self::BadRequest(kind, _) => ( Self::BadRequest(kind, _) => (
kind.clone(), kind.clone(),
match kind { match kind {
Forbidden | GuestAccessForbidden | ThreepidAuthFailed | ThreepidDenied => { WrongRoomKeysVersion { .. }
StatusCode::FORBIDDEN | Forbidden
} | GuestAccessForbidden
| ThreepidAuthFailed
| ThreepidDenied => StatusCode::FORBIDDEN,
Unauthorized | UnknownToken { .. } | MissingToken => StatusCode::UNAUTHORIZED, Unauthorized | UnknownToken { .. } | MissingToken => StatusCode::UNAUTHORIZED,
NotFound | Unrecognized => StatusCode::NOT_FOUND, NotFound | Unrecognized => StatusCode::NOT_FOUND,
LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS, LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,