Compare commits

...
53 Commits
Author SHA1 Message Date
Jason Volk 4fea0abac4 Bump 1.4.7
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 16:40:08 +00:00
Jason Volk cab0b3fd9f Reduce join-server selection randomness with separated sets.
Keep alias server biased at the front unless configured deprioritized.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:33 +00:00
Jason Volk f311332bad Add configuration for make_join and send_join attempt counts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:33 +00:00
Jason Volk fb0c2a2832 Allow clippy::useless-let-if-seq.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:29 +00:00
yefim 3403943880 Fix LDAP unauthorized 2025-12-02 18:34:18 +02:00
Jason Volk 7e69e9b051 Bump jemalloc.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:39:44 +00:00
Jason Volk 27ed9b88f1 Bump Complement.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:21:53 +00:00
Jason Volk 10edc3bd5e Disconnect jemalloc stats and prof until after default feature-shifting.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:21:53 +00:00
Jason Volk 6553ba829f Fix room-leave conditions for rooms without joined users or remote servers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:01:52 +00:00
Jason Volk 1ce3d2b01f Add ReadyBoolExt special case for ReadyEqExt.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:01:52 +00:00
Jason Volk 50bfb0fe5e Provide default for empty PathAndQuery (Sentry ID c09f58adf5...)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 05:20:26 +00:00
Jason Volk 74f8cd3708 Stop panic propagation on malloc_stats_print() callback.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 05:20:26 +00:00
Jason Volk bcc2be7661 docs: Use a !tip for the generic deployment sub-header.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-30 00:59:06 +00:00
Jason Volk 4f9b1d6dbd Add config options to coarsely disable log/tracing without initialization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-30 00:52:55 +00:00
Jason Volk c9362b8605 Optimize formatted event serializations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-29 12:26:49 +00:00
Jason Volk e84d6666c0 Optimize separate constraint for each stream argument.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 11:13:27 +00:00
Jason Volk 7666bb63d8 Optimize syncv3 joined room path with additional conditional queries.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 3125b7e291 Actually re-establish and assert Syncness of Error. (98affbdeaf)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk aebe2d72de Add utils for asserting common marker traits.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 8f54d9dc09 Replace unnecessary exchange util.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 52b156e034 Implement MSC4289/MSC4291 (room version 12) upgrade support. (closes #141)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 00:22:45 +00:00
Jason Volk d2d6a98180 Improve backfill server selection post-v12 power and creator changes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 00:22:45 +00:00
Jason Volk 0bbc228f7a Bump Ruma for context events_before serialization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 23:25:18 +00:00
Jason Volk 0d782095ad Refactor room upgrade endpoint; rollback on failure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 08:16:01 +00:00
Jason Volk f6b95ff1c4 Cleanup and optimize several user_can conditions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 08:16:01 +00:00
Jason Volk 347298d7d6 Additional json/jsint Error cases for ErrorKind and StatusCode.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 442a5aafeb Derive or impl fmt::Debug on several structs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 8bb0d02619 Replace calls through sender request interface.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 71f3ccf140 Unbox and pin database streams.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 98affbdeaf Re-establish Syncness of Error.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 03:02:26 +00:00
Jason Volk e5073165f0 Cleanup TryIgnore/TryExpect stream extensions related.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 03:02:26 +00:00
Jason Volk 6705efc760 Fix lib search path for liburing.a on redhat/centos.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 21:50:14 +00:00
Jason Volk 61085f4707 Bump RocksDB to 10.7.5-tuwunel.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:36:17 +00:00
Jason Volk deb5c65885 docker: Add conditions to set assert_linkage for Dockerfile.install
ci: Check packages when using test branch.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:36:17 +00:00
Jason Volk 11c4cbf54e docker: Build RocksDB with NUMA policy support.
docker: Build RocksDB with LTO.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:30:19 +00:00
Jason Volk a748edd621 Reduce high volume of strings from FedDest and ActualDest in resolver.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:30:19 +00:00
Jason Volk 9e539d0a22 Activate recursive relations. Maximum fan-out.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-21 03:16:01 +00:00
Jason Volk 5260912c3b Add admin configurable Access-Control-Allow-Origin.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk b924412efb Reduce tracing level of request handler return value.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk 120ab1d068 Add config circuit-breaker for heroes calculations during sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk e60e86e9ed Optimize heroes calculation using member state instead of timeline scan.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-19 02:14:35 +00:00
Jason Volk 5147b541b5 Fix relations pagination compliance.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-19 01:43:28 +00:00
Jason Volk f4eeaaf167 Remove unknown multilingual directive from book.toml.
Fix and improve mdbook related.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:46:25 +00:00
Jason Volk 54fe4bdf56 docker: Move complement logs into tests directory.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk fe12daead9 Fix short-circuit condition; propagate 403 in unlikely branch anyway. (fixes #219)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk 6f29a34ffb Fix existing event reinsertions during backfill handling.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk dafbe59d00 Return whether event already existed from event_handler.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk 0746f4b1ad Eliminate clone of Pdu to queue during relations recursions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
Jason Volk 90228e4865 Combine pdu_metadata Data into Service unit.
Move recursive component of get_relations from service to api crate.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
Jason Volk 53b5eb4ba6 ci: Re-enable lychee.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
dasha_uwuandJason Volk 20f080fc49 Fix typo. 2025-11-18 10:36:02 +00:00
Jason Volk 424ed3d7ad Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 03:56:37 +00:00
Tails32 728085bd1b Remove allow_check_for_updates from configs [ci skip]
to suppress a warning at startup due to removal of the update module in edb245a2ba
2025-11-16 01:05:08 +01:00
129 changed files with 2567 additions and 1833 deletions
-2
View File
@@ -106,7 +106,6 @@ jobs:
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
# disabled due to excessive build time issue installing cargo lychee
lychee:
if: >
!failure() && !cancelled()
@@ -114,7 +113,6 @@ jobs:
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
&& false
name: Lychee
uses: ./.github/workflows/bake.yml
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
publish: ${{vars.PUBLISH != 'false'}}
build_nix: ${{vars.BUILD_NIX != 'false'}}
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || contains(github.ref, 'tags/v')}}
check_pkgs: ${{vars.CHECK_PKGS || 'false'}}
check_pkgs: ${{vars.CHECK_PKGS || github.ref == 'refs/heads/test'}}
complement: ${{vars.COMPLEMENT != 'false'}}
complement_runner: 'het'
docker_repo: ${{vars.DOCKER_REPO}}
+1 -1
View File
@@ -395,7 +395,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: complement_output-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
path: ./complement.jsonl
path: ./tests/complement/logs.jsonl
if-no-files-found: ignore
- name: Accept
+1 -1
View File
@@ -94,7 +94,7 @@ public/
rustc-ice-*
# complement test logs are huge
tests/test_results/complement/test_logs.jsonl
tests/complement/logs.jsonl
# cargo profiles from -Z self-profile
*.mm_profdata
Generated
+475 -314
View File
File diff suppressed because it is too large Load Diff
+10 -28
View File
@@ -29,7 +29,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-construct/tuwunel"
rust-version = "1.89.0"
version = "1.4.6"
version = "1.4.7"
[workspace.metadata.crane]
name = "tuwunel"
@@ -231,6 +231,10 @@ version = "0.1"
[workspace.dependencies.itertools]
version = "0.14"
[workspace.dependencies.jevmalloc]
git = "https://github.com/matrix-construct/jevmalloc"
rev = "29fde02533ee2c6a5e9b87095324e6f7a5d43738"
[workspace.dependencies.jsonwebtoken]
version = "9.3"
default-features = false
@@ -320,7 +324,7 @@ default-features = false
[workspace.dependencies.ruma]
git = "https://github.com/matrix-construct/ruma"
rev = "db4bf59eaa1757735f38489f8924348ae05a6a17"
rev = "f8aa292ce48f28ee6c82b29a811d26c6ccb5aad2"
features = [
"__compat",
"appservice-api-c",
@@ -368,14 +372,16 @@ default-features = false
[workspace.dependencies.rust-rocksdb]
git = "https://github.com/matrix-construct/rust-rocksdb"
rev = "225a42519276e502205bdc845cebdb22d70ee245"
rev = "31e6f8dd4a531b379c132e82773efc9315b0489a"
default-features = false
features = [
"bzip2",
"lto",
"lz4",
"multi-threaded-cf",
"mt_static",
"serde1",
"zstd",
"zstd-static-linking-only",
]
[workspace.dependencies.sanitize-filename]
@@ -475,30 +481,6 @@ default-features = false
version = "2.0"
default-features = false
[workspace.dependencies.tikv-jemallocator]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemalloc-ctl]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = ["use_std"]
[workspace.dependencies.tikv-jemalloc-sys]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tokio]
version = "1.48"
default-features = false
+18 -26
View File
@@ -1,47 +1,39 @@
# Tuwunel 1.4.6
# Tuwunel 1.4.7
November 6, 2025
December 3, 2025
Tuwunel is now deployed at scale serving the citizens of Switzerland in production. Some optimizations were requested to reduce operating costs from projected growth over product lifespan: this release delivers with markedly reduced CPU usage and improves responsiveness. However, complications during routine dependency upgrades consumed valuable time planned for features and issues which could not be completed for this release.
### New Features
- Element Call discovery support was implemented by @tototomate123 in (#209). Adding a `[[global.well_known.rtc_transports]]` section in your [config file](https://github.com/matrix-construct/tuwunel/blob/e1f89b69ea117f166be423f035a5a34f4c0e7366/tuwunel-example.toml#L1835-L1851) enables discovery. More information on setting up Element Call can be found at [Spaetzblog](https://sspaeth.de/2024/11/sfu/), skipping step one, and performing step three in your Tuwunel config.
- Dehydrated Device support (MSC3814) is now available (#200). This feature allows users to receive encrypted messages without being logged in. Supporting clients will setup the dehydrated device automatically and it will "just work" behind the scenes; in fact, these clients will also hide it from the sessions list to avoid confusion. Support is not widespread yet but it has been tested with matrix-js-sdk clients such as Element-web. This feature was commissioned and made public by an enterprise sponsor.
- Notification panel (the 🔔 button) has been implemented in (#201). Even though Element-web now requires enabling it in the Labs menu, the underlying support (`GET /_matrix/client/v3/notifications`) enhances the push-notification handling of other clients.
- Live room previews are now available. This support (`GET /_matrix/client/v3/events`) allows users, including guests, to sync updates for a public room without joining (4afd6f347b1).
- Thanks to a suggestion by @cyberdoors in (#29), the configuration option `encryption_enabled_by_default_for_room_type` is now available. This feature can enable encryption for a room even when the client does not. The values `all` or `invite` are accepted, the latter roughly meaning DM's only. Neither are enabled by default.
- Upgrade Support for Room Version 12 is now available. Though this room version has been supported for the entire 1.4 series, all Tuwunel servers have been protected by Hydra Backports on all room versions. As such, other work was able to be prioritized for the preceding releases.
### Enhancements
- Thank you @tototomate123 for improving the reverse-proxy docs, adding dedicated pages for both [Caddy](https://github.com/matrix-construct/tuwunel/blob/e0a997c22784b453735b24907e607412b153ba56/docs/deploying/reverse-proxy-caddy.md) and [Nginx](https://github.com/matrix-construct/tuwunel/blob/e0a997c22784b453735b24907e607412b153ba56/docs/deploying/reverse-proxy-nginx.md) in (#209). Thanks to @tycode for pointing out the docs were missing for alternative proxies in (#197).
- Recursive relations have been enabled. This is an optimization which allows the server to gather more data using fewer client requests, for example, of a thread with many reactions and replies. The implementation is now optimal and utilizes the full capabilities of Tuwunel's asynchronous database.
- Thanks to an observation by @iwalkalone69 in (#40), the last-seen time for a device in the session list is now updated acceptably. This function piggybacks on the presence system to prevent writing too frequently; testing has never shown it more than a minute or few out of date.
- Several miscellaneous but significant optimizations took place at the direction of memory profiling. This reduced load on the allocator for database queries and JSON serialization of complex objects. Heroes calculations and the joined room hot-path on sync v3 were further optimized to reduce the database query load itself.
- Thanks to an inquiry by @EntityinArray in (#189) guest-accounts can now be enabled while registration tokens are also enabled to prevent fully open account registration. Note that registration tokens don't apply to guest-accounts and those are still fully open.
- Jemalloc has been repackaged with platform-specific optimizations enhancing the build. The upgrade to the dev branch of libjemalloc itself was considered as too much variability for the same release, it is planned for an upcoming release.
- Courtesy of @dasha-uwu the list of servers attempted when joining a room is now properly shuffled to increase the odds of finding a viable server, especially if an additional join attempt is made.
- Thanks to element-hq/synapse#18970 by @dasha-uwu, we have very slightly turned down the amount of randomness when selecting join-servers, More retries also occur within a single request if necessary. Thanks to @gogo199432 and @lifeofguenter for reporting problems in (#128) and (#205) respectively. More opportunities are still available to make large room joins robust.
### Bug Fixes
- Special thanks to @BVollmerhaus for finding the TURN secret file configured by `turn_secret_file` was broken in (#211), forcing users to configure `turn_secret` directly. Thank you for fixing this in (#212).
- Special thanks to @yefimg for fixing LDAP logout in (#231) from a report kindly made by @orhtej2 in (#97); thank you for your patience waiting for domain expertise to assist here.
- Thank you @scvalex for updating the nix build for Tuwunel's integration tests and re-enabling all checks. (#215)
- Thanks to @Radiant-Xyz the example configurations have been updated to remove `allow_check_for_updates`. This fixes any warnings for the item no longer existing. (#221)
- Thanks to a report by @Anagastes in (#146) **Nheko and NeoChat users can now enjoy properly verified devices.** Special thanks for the assistance of @deepbluev7 with diagnosing the cross-signing signature issue.
- Thanks again to @Radiant-Xyz reporting in (#219) the `/whoami` endpoint is now returns spec-compliant errors for Mautrix bridges (fe12daead9). Thanks also to @bobobo1618 for confirming the fix is working.
- Database columns intended for deletion, notably `roomsynctoken_shortstatehash`, never had the deletion command actually invoked on them 😭 explaining the lack of enthusiasm after the 1.4.3 release introduced stateless sync. **Users will now see the free disk space they were promised.** This was uncovered during an unrelated issue investigation courtesy of @frebib.
- Relations responses were sometimes incorrect in the forwards direction. This was fixed by (5147b541) bringing those responses into full compliance. Note the prior release had also fixed compliance issues but in the backwards direction.
- Thanks to investigation by @dasha-uwu the pagination tokens in the `/relations` endpoint were buggy and now operate correctly.
- Server selection for backfill struggled sometimes for version 12 rooms. These rooms might fail to load history after join. Additional servers are now found using `creators` and `additional_creators` instead.
- Thanks to @Polve for identifying the `DynamicUser=yes` directive in the systemd files was invalid and advising a replacement in (#207).
- Room leave compliance has been fixed for an edge-case where a room becomes empty except for a locally invited user which does not have its leave event sent down `/sync`.
- Thanks to @daudix for reporting an edge-case where the server will refuse to start rather than robustly reporting errors during startup checks and recreate a missing media directory (#213).
- Thanks to @grinapo for a report which lead to the discovery of events acquired over backfill not being checked for whether they already exist.
- Push rule evaluation was never implemented for invites arriving over federation. Notifications are now properly sent in this case.
### Upcoming
- Sliding-sync handlers were susceptible to errors under rare circumstances escaping to cause an HTTP 500, which wreaks havoc on the rust-sdk. This has now been prevented.
- Federating with Conduit over several non-essential endpoints was broken. It is unclear whether this affected an actual Conduit release version, but thanks to @kladki a fix is scheduled and we have included a workaround now on this end.
- As stated in the summary, several planned items could not be cut into this release. These include SSO/OIDC support (#7), Element Call setup assistance and documentation (#217)(#215), User-level Admin Room and Media deletion (#192), and any other assigned issue. These items are on the short-list for the next cycle and mean a lot to us; to all participants: your issues are not being ignored and we hear you.
+3 -2
View File
@@ -5,7 +5,6 @@ authors = [
]
description = "Tuwunel, a high performance successor to Conduit and Conduwuit"
language = "en"
multilingual = false
src = "docs"
title = "Tuwunel One"
text-direction = "ltr"
@@ -15,7 +14,9 @@ build-dir = "public"
create-missing = true
extra-watch-dirs = [
"debian",
"docker",
"docs",
"rpm",
]
[rust]
@@ -24,7 +25,7 @@ edition = "2024"
[output.html]
git-repository-url = "https://github.com/matrix-construct/tuwunel"
edit-url-template = "https://github.com/matrix-construct/tuwunel/edit/main/{path}"
git-repository-icon = "fa-github-square"
git-repository-icon = "fab-github"
[output.html.search]
limit-results = 15
+6 -6
View File
@@ -1,11 +1,11 @@
stack-size-threshold = 393216
future-size-threshold = 24576
array-size-threshold = 4096
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
excessive-nesting-threshold = 8
future-size-threshold = 8192
stack-size-threshold = 196608 # TODO reduce me ALARA
too-many-lines-threshold = 780 # TODO reduce me to <= 100
type-complexity-threshold = 250 # reduce me to ~200
large-error-threshold = 256 # TODO reduce me ALARA
too-many-lines-threshold = 780 # TODO reduce me to <= 100
excessive-nesting-threshold = 8
type-complexity-threshold = 250 # reduce me to ~200
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
#disallowed-macros = [
# { path = "log::error", reason = "use tuwunel_core::error" },
+1 -1
View File
@@ -128,7 +128,7 @@ ENTRYPOINT valgrind \
FROM input AS complement-base
ARG var_cache
ARG var_lib_apt
ARG complement_ref="426bc3458e5287398ec39ab7d2b8f2e3103dcf39"
ARG complement_ref="4d3130f06d0dc3f794b5d48fbdba0b466792b52b"
ARG complement_tags="conduwuit_blacklist"
ARG complement_tests="./tests/..."
ARG complement_run=".*"
+6 -4
View File
@@ -15,15 +15,17 @@ ENV src_path="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}/tuwunel
ENV dst_path="${install_prefix}/bin/tuwunel"
COPY --from=bins $src_path $dst_path
RUN <<EOF
ret=$(ldd "${dst_path}")
ldd -v "${dst_path}"
ret=$?
if [ "$ret" = "0" ] && [ "$assert_linkage" = "static" ]; then
echo "($ret) expected a static binary"
exit 1
elif [ "$ret" != "0" ] && [ "$assert_linkage" = "dynamic" ]; then
echo "($ret) expected a dynamic binary"
exit 1
fi
set -eux
ldd -v ${dst_path} || true
du -h ${dst_path}
sha1sum ${dst_path}
du -h "${dst_path}"
sha1sum "${dst_path}"
EOF
+4 -1
View File
@@ -28,10 +28,12 @@ FROM input AS rocksdb-build
ARG rocksdb_shared=0
ARG rocksdb_portable="1"
ARG rocksdb_opt_level="3"
ARG rocksdb_lto="-flto -ffat-lto-objects"
ARG rocksdb_build_type="Release"
ARG rocksdb_cxx_flags="-ftls-model=initial-exec"
ARG rocksdb_make_verbose="ON"
ARG rocksdb_make_rule_messages="OFF"
ARG rocksdb_numa=0
ARG rocksdb_jemalloc=1
ARG rocksdb_iouring=1
ARG rocksdb_zstd=1
@@ -59,7 +61,7 @@ RUN <<EOF
"-DBUILD_SHARED_LIBS=${rocksdb_shared}" \
"-DROCKSDB_BUILD_SHARED=${rocksdb_shared}" \
"-DCMAKE_CXX_FLAGS:STRING=${rocksdb_cxx_flags}" \
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG" \
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG ${rocksdb_lto}" \
"-DPORTABLE=${rocksdb_portable}" \
"-DFAIL_ON_WARNINGS=0" \
"-DUSE_RTTI=0" \
@@ -73,6 +75,7 @@ RUN <<EOF
"-DWITH_TOOLS=0" \
"-DWITH_TESTS=0" \
"-DWITH_GFLAGS=0" \
"-DWITH_NUMA=${rocksdb_numa}" \
"-DWITH_LIBURING=${rocksdb_iouring}" \
"-DWITH_JEMALLOC=${rocksdb_jemalloc}" \
"-DWITH_ZSTD=${rocksdb_zstd}" \
+17 -5
View File
@@ -84,6 +84,9 @@ variable "rocksdb_build_type" {
variable "rocksdb_make_verbose" {
default = "ON"
}
variable "rocksdb_numa" {
default = "0"
}
# Complement options
variable "complement_count" {
@@ -865,6 +868,12 @@ target "install" {
}
args = {
install_prefix = install_prefix
assert_linkage = (
substr(cargo_profile, 0, 5) == "bench"? "static":
substr(cargo_profile, 0, 7) == "release"? "static":
substr(rust_toolchain, 0, 6) == "stable"? "static":
""
)
}
}
@@ -1411,7 +1420,7 @@ target "deps-base" {
join(" ", static_rustflags),
join(" ", static_nightly_rustflags),
join(" ", native_rustflags),
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
@@ -1433,7 +1442,7 @@ target "deps-base" {
join(" ", static_nightly_rustflags),
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
@@ -1453,7 +1462,7 @@ target "deps-base" {
join(" ", static_rustflags),
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
@@ -1473,7 +1482,7 @@ target "deps-base" {
join(" ", static_rustflags),
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
@@ -1552,6 +1561,7 @@ target "rocksdb-build" {
rocksdb_zstd = contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
rocksdb_jemalloc = contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? 1: 0
rocksdb_iouring = contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? 1: 0
rocksdb_numa = rocksdb_numa
rocksdb_shared = 0
rocksdb_opt_level = rocksdb_opt_level
rocksdb_build_type = rocksdb_build_type
@@ -1716,7 +1726,7 @@ cargo_installs = [
"cargo-deb",
"cargo-generate-rpm",
"cargo-valgrind",
#"lychee",
"lychee",
"mdbook",
"typos-cli",
]
@@ -1805,6 +1815,8 @@ kitchen_packages = [
"gzip",
"jq",
"libc6-dev",
"libclang-dev",
"libnuma-dev",
"libssl-dev",
"libsqlite3-dev",
"make",
+1 -1
View File
@@ -82,7 +82,7 @@ if test "$CI" = "true"; then
fi
output_src="$cid:/usr/src/complement/full_output.jsonl"
output_dst="complement.jsonl"
output_dst="tests/complement/logs.jsonl"
extract_output() {
docker cp "$output_src" "$output_dst"
}
@@ -20,7 +20,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
@@ -36,7 +36,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
@@ -26,7 +26,6 @@ services:
# TUWUNEL_LOG: info # default is: "warn,state_res=warn"
# TUWUNEL_ALLOW_ENCRYPTION: 'true'
# TUWUNEL_ALLOW_FEDERATION: 'true'
# TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
# TUWUNEL_ALLOW_INCOMING_PRESENCE: true
# TUWUNEL_ALLOW_OUTGOING_PRESENCE: true
# TUWUNEL_ALLOW_LOCAL_PRESENCE: true
-1
View File
@@ -20,7 +20,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
+3 -3
View File
@@ -1,9 +1,9 @@
# Generic deployment documentation
> ### Getting help
> [!TIP]
>
> If you run into any problems while setting up Tuwunel [open an issue on
> GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
> Getting help: If you run into any problems while setting up Tuwunel
> [open an issue on GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
## Installing Tuwunel
+1 -1
View File
@@ -1,5 +1,5 @@
[Container]
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_ALLOW_CHECK_FOR_UPDATES=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
Image=docker.io/jevolk/tuwunel:latest
PublishPort=8448:6167
Volume=/path/to/db:/var/lib/tuwunel
Generated
+27 -27
View File
@@ -32,11 +32,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1761150191,
"narHash": "sha256-fPQmV+K836jR/Tu6slfMdm9HL3p0IEPB5x5I7Zc6v5E=",
"lastModified": 1763236786,
"narHash": "sha256-JB19RGXDr6loKSdqwvA15jhRHwf6+9Crq2glqqVar84=",
"owner": "cachix",
"repo": "cachix",
"rev": "5629a7d84c91df9c06650b18eb428cce4166c4fc",
"rev": "938a275857047c300596092beaabaee6d892e243",
"type": "github"
},
"original": {
@@ -85,11 +85,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1756358878,
"narHash": "sha256-H/4TkbFcGsMRDZ7gNaXZ7NxeO3b1YgkVt9zPb+3ATSg=",
"lastModified": 1761739261,
"narHash": "sha256-XdzSBbJIYG6thrHbo44/qBiMu5R4bayfy/dlWo9AXBA=",
"owner": "matrix-construct",
"repo": "complement",
"rev": "403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac",
"rev": "350d7666cab14cb0051ef53da7a1b0b3216d7269",
"type": "github"
},
"original": {
@@ -116,11 +116,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1760924934,
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
"lastModified": 1763511871,
"narHash": "sha256-KKZWi+ij7oT0Ag8yC6MQkzfHGcytyjMJDD+47ZV1YNU=",
"owner": "ipetkov",
"repo": "crane",
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
"rev": "099f9014bc8d0cd6e445470ea1df0fd691d5a548",
"type": "github"
},
"original": {
@@ -170,11 +170,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1761547629,
"narHash": "sha256-4OH1CVm2PdjKRqEJ3RLfkQMDSBdn7VId6iyYCwKOK+U=",
"lastModified": 1763707297,
"narHash": "sha256-Bd9VGavwFBLpyU4pjiWfv73gUibNj8dc3xmOW8ff3bI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "d82a7c64ea441e397914577c9a18f2867e5b364b",
"rev": "7c2d3a165a4a080fdcb6c191d8f9768281c99f75",
"type": "github"
},
"original": {
@@ -219,11 +219,11 @@
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -346,11 +346,11 @@
"liburing": {
"flake": false,
"locked": {
"lastModified": 1761487364,
"narHash": "sha256-PmRdt9dQUFGcVrX7XCh7z2FpRm1SqKgiYbIwpkJPjRU=",
"lastModified": 1763758538,
"narHash": "sha256-cDsxLOqeC7imBcArolTHvejSnWoadgpvDy1DJ2/3MOw=",
"owner": "axboe",
"repo": "liburing",
"rev": "65ec4223032a38870dde2bce646ba4de13d2feff",
"rev": "e1ef1e680ee38ed9116989155fca47921698c25f",
"type": "github"
},
"original": {
@@ -493,11 +493,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1761349956,
"narHash": "sha256-tH3wHnOJms+U4k/rK2Nn1RfBrhffX92jLP/2VndSn0w=",
"lastModified": 1763618868,
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "02f2cb8e0feb4596d20cc52fda73ccee960e3538",
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
"type": "github"
},
"original": {
@@ -510,11 +510,11 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1749358049,
"narHash": "sha256-ZSjvAZBfZkJrBIpw8ANZMbJVb8AeuogvuAipGVE4Qe4=",
"lastModified": 1763593074,
"narHash": "sha256-aOV/jJjRjNJ3hrRqhCsXlIz05NvEhDF/j5Q5UOQuvp8=",
"owner": "matrix-construct",
"repo": "rocksdb",
"rev": "cf7f65d0b377af019661c240f9165b3ef60640c3",
"rev": "9a3a213b55df0b11408102c899a940675c0d90e4",
"type": "github"
},
"original": {
@@ -542,11 +542,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1761500479,
"narHash": "sha256-syeBTCCU96qPJHcVpwHeCwmPCiLTDHHgYQYhpZ0iwLo=",
"lastModified": 1763648203,
"narHash": "sha256-/WJdebbRD+m5vr2xy/bJdCpqd7YHSMapjuXAM/0lvtA=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "049767e6faa84b2d1a951d8f227e6ebd99d728a2",
"rev": "eaaa2da9fbbfd7a79ff501e0563351cb2004574a",
"type": "github"
},
"original": {
-1
View File
@@ -21,7 +21,6 @@ media_startup_check = true
prune_missing_media = true
log_colors = true
admin_room_notices = false
allow_check_for_updates = false
intentionally_unknown_config_option_for_testing = true
rocksdb_log_level = "info"
rocksdb_max_log_files = 1
+6 -9
View File
@@ -236,8 +236,8 @@ pub(super) async fn get_remote_pdu(
match self
.services
.sending
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
.federation
.execute(&server, ruma::api::federation::event::get_event::v1::Request {
event_id: event_id.clone(),
})
.await
@@ -327,11 +327,8 @@ pub(super) async fn ping(&self, server: OwnedServerName) -> Result {
match self
.services
.sending
.send_federation_request(
&server,
ruma::api::federation::discovery::get_server_version::v1::Request {},
)
.federation
.execute(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.await
{
| Err(e) => {
@@ -571,8 +568,8 @@ pub(super) async fn force_set_room_state_from_server(
let remote_state_response = self
.services
.sending
.send_federation_request(&server_name, get_room_state::v1::Request {
.federation
.execute(&server_name, get_room_state::v1::Request {
room_id: room_id.clone(),
event_id: first_pdu.event_id().to_owned(),
})
+7 -5
View File
@@ -5,7 +5,7 @@
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
};
use tuwunel_core::{Err, Result, info, utils::ReadyExt};
use tuwunel_core::{Err, Result, err, info, utils::ReadyExt};
use crate::{Ruma, router::auth_uiaa};
@@ -73,10 +73,12 @@ pub(crate) async fn whoami_route(
Ok(whoami::v3::Response {
user_id: body.sender_user().to_owned(),
device_id: body.sender_device.clone(),
is_guest: services
.users
.is_deactivated(body.sender_user())
.await? && body.appservice_info.is_none(),
is_guest: body.appservice_info.is_none()
&& services
.users
.is_deactivated(body.sender_user())
.await
.map_err(|_| err!(Request(Forbidden("User does not exist."))))?,
})
}
+4 -7
View File
@@ -37,13 +37,10 @@ pub(crate) async fn appservice_ping(
let timer = tokio::time::Instant::now();
let _response = services
.sending
.send_appservice_request(
appservice_info.registration.clone(),
ping::send_ping::v1::Request {
transaction_id: body.transaction_id.clone(),
},
)
.appservice
.send_request(appservice_info.registration.clone(), ping::send_ping::v1::Request {
transaction_id: body.transaction_id.clone(),
})
.await?
.expect("We already validated if an appservice URL exists above");
+2 -2
View File
@@ -220,8 +220,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
server.filter(|server_name| !services.globals.server_is_ours(server_name))
{
let response = services
.sending
.send_federation_request(
.federation
.execute(
other_server,
federation::directory::get_public_rooms_filtered::v1::Request {
limit,
+3 -6
View File
@@ -503,10 +503,7 @@ pub(crate) async fn get_keys_helper<F>(
let request =
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
let response = services
.sending
.send_federation_request(server, request)
.await;
let response = services.federation.execute(server, request).await;
(server, response)
})
@@ -631,8 +628,8 @@ pub(crate) async fn claim_keys_helper(
(
server,
services
.sending
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
.federation
.execute(server, federation::keys::claim_keys::v1::Request {
one_time_keys: one_time_keys_input_fed,
})
.await,
+27 -5
View File
@@ -5,7 +5,7 @@
RoomId, RoomOrAliasId,
api::client::membership::{join_room_by_id, join_room_by_id_or_alias},
};
use tuwunel_core::Result;
use tuwunel_core::{Result, warn};
use super::banned_room_check;
use crate::{Ruma, client::membership::get_join_params};
@@ -36,7 +36,8 @@ pub(crate) async fn join_room_by_id_route(
let state_lock = services.state.mutex.lock(&room_id).await;
services
let mut errors = 0_usize;
while let Err(e) = services
.membership
.join(
sender_user,
@@ -47,7 +48,17 @@ pub(crate) async fn join_room_by_id_route(
&state_lock,
)
.boxed()
.await?;
.await
{
errors = errors.saturating_add(1);
if errors >= services.config.max_join_attempts_per_join_request {
warn!(
"Several servers failed. Giving up for this request. Try again for different \
server selection."
);
return Err(e);
}
}
drop(state_lock);
@@ -80,7 +91,8 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let state_lock = services.state.mutex.lock(&room_id).await;
services
let mut errors = 0_usize;
while let Err(e) = services
.membership
.join(
sender_user,
@@ -91,7 +103,17 @@ pub(crate) async fn join_room_by_id_or_alias_route(
&state_lock,
)
.boxed()
.await?;
.await
{
errors = errors.saturating_add(1);
if errors >= services.config.max_join_attempts_per_join_request {
warn!(
"Several servers failed. Giving up for this request. Try again for different \
server selection."
);
return Err(e);
}
}
drop(state_lock);
+13 -16
View File
@@ -260,8 +260,8 @@ async fn knock_room_helper_local(
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.federation
.execute(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
@@ -396,8 +396,8 @@ async fn knock_room_helper_remote(
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.federation
.execute(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
@@ -556,18 +556,15 @@ async fn make_knock_request(
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
let make_knock_response = services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_knock_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services
.server
.supported_room_versions()
.collect(),
},
)
.federation
.execute(remote_server, federation::membership::prepare_knock_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services
.server
.supported_room_versions()
.collect(),
})
.await;
trace!("make_knock response: {make_knock_response:?}");
+18 -5
View File
@@ -151,7 +151,7 @@ async fn get_join_params(
via: &[OwnedServerName],
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
// servers tried first, additional_servers shuffled then tried after
let (room_id, mut servers, mut additional_servers) =
let (room_id, mut primary_servers, mut additional_servers) =
match OwnedRoomId::try_from(room_id_or_alias.to_owned()) {
// if room id, shuffle via + room_id server_name ...
| Ok(room_id) => {
@@ -194,11 +194,23 @@ async fn get_join_params(
.map(|user| user.server_name().to_owned()),
);
primary_servers.sort_unstable();
primary_servers.dedup();
shuffle(&mut primary_servers);
// shuffle additionals, append to base servers
servers.append(&mut additional_servers);
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
additional_servers.sort_unstable();
additional_servers.dedup();
shuffle(&mut additional_servers);
let mut servers: Vec<_> = room_id_or_alias
.server_name()
.filter(|_| room_id_or_alias.is_room_alias_id())
.map(ToOwned::to_owned)
.into_iter()
.chain(primary_servers.into_iter())
.chain(additional_servers.into_iter())
.collect();
// sort deprioritized servers last
servers.sort_by(|a, b| {
@@ -207,6 +219,7 @@ async fn get_join_params(
.config
.deprioritize_joins_through_servers
.is_match(a.host());
let b_matches = services
.server
.config
+17 -12
View File
@@ -1,5 +1,9 @@
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, pin_mut};
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::{Either, OptionFuture},
pin_mut,
};
use ruma::{
RoomId, UserId,
api::{
@@ -105,17 +109,18 @@ pub(crate) async fn get_message_events_route(
}
let it = match body.dir {
| Direction::Forward => services
.timeline
.pdus(Some(sender_user), room_id, Some(from))
.ignore_err()
.boxed(),
| Direction::Backward => services
.timeline
.pdus_rev(Some(sender_user), room_id, Some(from))
.ignore_err()
.boxed(),
| Direction::Forward => Either::Left(
services
.timeline
.pdus(Some(sender_user), room_id, Some(from))
.ignore_err(),
),
| Direction::Backward => Either::Right(
services
.timeline
.pdus_rev(Some(sender_user), room_id, Some(from))
.ignore_err(),
),
};
let events: Vec<_> = it
+6 -6
View File
@@ -68,8 +68,8 @@ pub(crate) async fn get_displayname_route(
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
.federation
.execute(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
@@ -169,8 +169,8 @@ pub(crate) async fn get_avatar_url_route(
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
.federation
.execute(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
@@ -231,8 +231,8 @@ pub(crate) async fn get_profile_route(
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
.federation
.execute(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
+114 -58
View File
@@ -1,5 +1,11 @@
use std::iter::once;
use axum::extract::State;
use futures::StreamExt;
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::try_join3,
stream::{select_all, unfold},
};
use ruma::{
EventId, RoomId, UInt, UserId,
api::{
@@ -12,12 +18,16 @@
events::{TimelineEventType, relation::RelationType},
};
use tuwunel_core::{
Result, at,
Err, Error, Result, at, err,
matrix::{
event::{Event, RelationTypeEqual},
pdu::PduCount,
pdu::{PduCount, PduId},
},
utils::{
BoolExt,
result::FlatOk,
stream::{ReadyExt, WidebandExt},
},
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
};
use tuwunel_service::Services;
@@ -99,6 +109,13 @@ pub(crate) async fn get_relating_events_route(
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(
name = "relations",
level = "debug",
skip_all,
fields(room_id, target, from, to, dir, limit, recurse),
ret(level = "trace")
)]
async fn paginate_relations_with_filter(
services: &Services,
sender_user: &UserId,
@@ -112,76 +129,115 @@ async fn paginate_relations_with_filter(
recurse: bool,
dir: Direction,
) -> Result<get_relating_events::v1::Response> {
let start: PduCount = from
.map(str::parse)
.transpose()?
.unwrap_or_else(|| match dir {
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let from: Option<PduCount> = from.map(str::parse).transpose()?;
let to: Option<PduCount> = to.map(str::parse).flat_ok();
// Use limit or else 30, with maximum 100
// Spec (v1.10) recommends depth of at least 3
let max_depth: usize = if recurse { 3 } else { 0 };
let limit: usize = limit
.map(TryInto::try_into)
.flat_ok()
.unwrap_or(30)
.min(100);
// Spec (v1.10) recommends depth of at least 3
let depth: u8 = if recurse { 3 } else { 1 };
let target = services
.timeline
.get_pdu_id(target)
.map_ok(PduId::from)
.map_ok(Ok::<_, Error>);
let events: Vec<_> = services
.pdu_metadata
.get_relations(sender_user, room_id, target, start, limit, depth, dir)
.await
.into_iter()
.filter(|(_, pdu)| {
filter_event_type
.as_ref()
.is_none_or(|kind| kind == pdu.kind())
})
.filter(|(_, pdu)| {
filter_rel_type
.as_ref()
.is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
})
.stream()
.ready_take_while(|(count, _)| Some(*count) != to)
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
.take(limit)
.collect()
.await;
let visible = services
.state_accessor
.user_can_see_state_events(sender_user, room_id)
.map(|visible| {
visible.ok_or_else(|| err!(Request(Forbidden("You cannot view this room."))))
});
let next_batch = events
.last()
.map(at!(0))
.as_ref()
.map(ToString::to_string);
let shortroomid = services.short.get_shortroomid(room_id);
let (shortroomid, target, ()) = try_join3(shortroomid, target, visible).await?;
let Ok(target) = target else {
return Ok(get_relating_events::v1::Response::new(Vec::new()));
};
if shortroomid != target.shortroomid {
return Err!(Request(NotFound("Event not found in room.")));
}
if let PduCount::Backfilled(_) = target.count {
return Ok(get_relating_events::v1::Response::new(Vec::new()));
}
let fetch = |depth: usize, count: PduCount| {
services
.pdu_metadata
.get_relations(shortroomid, count, from, dir, Some(sender_user))
.map(move |(count, pdu)| (depth, count, pdu))
.ready_filter(|(_, count, _)| matches!(count, PduCount::Normal(_)))
.boxed()
};
let events = unfold(select_all(once(fetch(0, target.count))), async |mut relations| {
let (depth, count, pdu) = relations.next().await?;
if depth < max_depth {
relations.push(fetch(depth.saturating_add(1), count));
}
Some(((depth, count, pdu), relations))
})
.ready_take_while(|&(_, count, _)| Some(count) != to)
.ready_filter(|(_, _, pdu)| {
filter_event_type
.as_ref()
.is_none_or(|kind| kind == pdu.kind())
})
.ready_filter(|(_, _, pdu)| {
filter_rel_type
.as_ref()
.is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
})
.wide_filter_map(async |(depth, count, pdu)| {
services
.state_accessor
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.await
.then_some((depth, count, pdu))
})
.take(limit)
.collect::<Vec<_>>()
.await;
Ok(get_relating_events::v1::Response {
next_batch,
prev_batch: from.map(Into::into),
recursion_depth: recurse.then_some(depth.into()),
recursion_depth: max_depth
.gt(&0)
.then(|| events.iter().map(at!(0)))
.into_iter()
.flatten()
.max()
.map(TryInto::try_into)
.transpose()?,
next_batch: events
.last()
.map(at!(1))
.as_ref()
.map(ToString::to_string),
prev_batch: events
.first()
.map(at!(1))
.or(from)
.as_ref()
.map(ToString::to_string),
chunk: events
.into_iter()
.map(at!(1))
.map(at!(2))
.map(Event::into_format)
.collect(),
})
}
async fn visibility_filter<Pdu: Event>(
services: &Services,
sender_user: &UserId,
item: (PduCount, Pdu),
) -> Option<(PduCount, Pdu)> {
let (_, pdu) = &item;
services
.state_accessor
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.await
.then_some(item)
}
+2 -2
View File
@@ -232,8 +232,8 @@ async fn remote_room_summary_hierarchy_response(
.iter()
.map(|server| {
services
.sending
.send_federation_request(server, request.clone())
.federation
.execute(server, request.clone())
})
.collect();
+420 -185
View File
@@ -1,42 +1,63 @@
use std::cmp::max;
use axum::extract::State;
use futures::StreamExt;
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId,
api::client::room::upgrade_room,
CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UserId,
api::client::room::upgrade_room::v3,
events::{
StateEventType, TimelineEventType,
room::{
create::PreviousRoom,
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent,
},
},
int,
room_version_rules::RoomIdFormatVersion,
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
};
use serde_json::{
Value as JsonValue, json,
value::{to_raw_value, to_value},
};
use serde_json::{json, value::to_raw_value};
use tuwunel_core::{
Err, Result, err,
Err, Result, debug_info, err, error, implement, info, is_equal_to, is_less_than,
matrix::{Event, StateKey, pdu::PduBuilder, room_version},
utils::{
future::TryExtExt,
stream::{IterStream, ReadyExt, WidebandExt},
},
};
use tuwunel_service::{Services, rooms::timeline::RoomMutexGuard};
use crate::Ruma;
/// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
StateEventType::RoomAvatar,
//TODO: Upgrade Ruma
const RECOMMENDED_TRANSFERABLE_STATE_EVENT_TYPES: &[StateEventType; 9] = &[
StateEventType::RoomServerAcl,
StateEventType::RoomEncryption,
StateEventType::RoomName,
StateEventType::RoomAvatar,
StateEventType::RoomTopic,
StateEventType::RoomGuestAccess,
StateEventType::RoomHistoryVisibility,
StateEventType::RoomJoinRules,
StateEventType::RoomName,
StateEventType::RoomPowerLevels,
StateEventType::RoomServerAcl,
StateEventType::RoomTopic,
];
#[derive(Debug)]
struct RoomUpgradeContext<'a> {
services: &'a Services,
sender_user: &'a UserId,
old_room_id: &'a RoomId,
old_state_lock: &'a RoomMutexGuard,
new_room_id: &'a RoomId,
new_state_lock: &'a RoomMutexGuard,
new_version_rules: &'a RoomVersionRules,
additional_creators: &'a [OwnedUserId],
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
///
/// Upgrades the room.
@@ -47,117 +68,227 @@
/// - Transfers some state events
/// - Moves local aliases
/// - Modifies old room power levels to prevent users from speaking
#[tracing::instrument(level = "debug")]
pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
debug_assert!(
TRANSFERABLE_STATE_EVENTS.is_sorted(),
"TRANSFERABLE_STATE_EVENTS is not sorted"
);
body: Ruma<v3::Request>,
) -> Result<v3::Response> {
let sender_user = body.sender_user();
let new_version = &body.new_version;
let version_rules = room_version::rules(new_version)?;
if !services
.server
.supported_room_version(&body.new_version)
.supported_room_version(new_version)
{
return Err!(Request(UnsupportedRoomVersion(
"This server does not support that room version.",
)));
}
if matches!(body.new_version, RoomVersionId::V12) {
return Err!(Request(UnsupportedRoomVersion(
"Upgrading to version 12 is still under development.",
)));
let old_room_id = &body.room_id;
let old_state_lock = services.state.mutex.lock(old_room_id).await;
if !services
.state_accessor
.user_can_tombstone(old_room_id, sender_user, &old_state_lock)
.await
{
return Err!(Request(Forbidden("You are not permitted to upgrade the room.")));
}
let room_version_rules = room_version::rules(&body.new_version)?;
let room_id_format = &room_version_rules.room_id_format;
assert!(*room_id_format == RoomIdFormatVersion::V1, "TODO");
// Create a replacement room
let replacement_room = RoomId::new_v1(services.globals.server_name());
let _short_id = services
.short
.get_or_create_shortroomid(&replacement_room)
.await;
let state_lock = services.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
// permissions
let tombstone_event_id = services
let latest_event = services
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.clone(),
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
.latest_pdu_in_room(old_room_id)
.await
.ok();
// Change lock to replacement room
drop(state_lock);
let state_lock = services.state.mutex.lock(&replacement_room).await;
let predecessor = PreviousRoom {
room_id: old_room_id.to_owned(),
event_id: latest_event
.as_ref()
.map(Event::event_id)
.map(ToOwned::to_owned),
};
debug_info!(
%sender_user,
%old_room_id,
last_event = ?predecessor.event_id,
?new_version,
"Attempting upgrade of room..."
);
let id_format = version_rules.room_id_format;
let (replacement_room, state_lock) = match id_format {
| RoomIdFormatVersion::V2 =>
upgrade_room_create(
&services,
sender_user,
old_room_id,
new_version,
&version_rules,
predecessor,
body.additional_creators.clone(),
)
.await,
| RoomIdFormatVersion::V1 =>
upgrade_room_create_legacy(
&services,
sender_user,
old_room_id,
new_version,
&version_rules,
predecessor,
)
.await,
}
.inspect_err(|e| error!(?body, "Upgrade m.room.create event failed: {e}"))?;
let context = RoomUpgradeContext {
services: &services,
sender_user,
old_room_id: &body.room_id,
old_state_lock: &old_state_lock,
new_room_id: &replacement_room,
new_state_lock: &state_lock,
new_version_rules: &version_rules,
additional_creators: &body.additional_creators,
};
if let Err(e) = context.transfer_room().await {
error!(?e, ?context, "Room upgrade failed. Cleaning up incomplete room...");
if let Err(e) = services
.delete
.delete_room(&replacement_room, false, state_lock)
.await
{
error!("Additional errors while deleting incomplete room: {e}");
}
return Err(e);
}
info!(
old_room_id = %context.old_room_id,
new_room_id = %context.new_room_id,
upgraded_by = %sender_user,
"Room upgraded",
);
Ok(v3::Response { replacement_room })
}
#[tracing::instrument(level = "info")]
async fn upgrade_room_create(
services: &Services,
sender_user: &UserId,
old_room_id: &RoomId,
new_version: &RoomVersionId,
version_rules: &RoomVersionRules,
predecessor: PreviousRoom,
mut additional_creators: Vec<OwnedUserId>,
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
let mut content: CanonicalJsonObject = services
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
.room_state_get_content(old_room_id, &StateEventType::RoomCreate, "")
.await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
// Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
body.room_id.clone(),
Some(tombstone_event_id),
));
content.remove("creator");
content.insert("predecessor".into(), json!(predecessor).try_into()?);
content.insert("room_version".into(), json!(new_version).try_into()?);
// Send a m.room.create event containing a predecessor field and the applicable
// room_version
if version_rules
.authorization
.additional_room_creators
{
use RoomVersionId::*;
match body.new_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
create_event_content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
err!(Request(BadJson(error!("Error forming creation event: {e}"))))
})?,
);
},
| _ => {
// "creator" key no longer exists in V11+ rooms
create_event_content.remove("creator");
},
additional_creators.sort();
additional_creators.dedup();
content.remove("additional_creators");
if !additional_creators.is_empty() {
content.insert("additional_creators".into(), json!(additional_creators).try_into()?);
}
}
create_event_content.insert(
"room_version".into(),
json!(&body.new_version)
.try_into()
.map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
);
create_event_content.insert(
"predecessor".into(),
json!(predecessor)
.try_into()
.map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
);
// Validate creation event content
let raw_content = to_raw_value(&content)?;
if let Err(e) = serde_json::from_str::<CanonicalJsonObject>(raw_content.get()) {
return Err!(Request(BadJson("Error forming creation event: {e}")));
}
let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
let state_lock = services.state.mutex.lock(&room_id).await;
let create_event_id = services
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&content)?,
state_key: Some(StateKey::new()),
..Default::default()
},
sender_user,
&room_id,
&state_lock,
)
.boxed()
.await?;
drop(state_lock);
// The real room_id is now the event_id.
let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
let state_lock = services.state.mutex.lock(&room_id).await;
Ok((room_id, state_lock))
}
#[tracing::instrument(level = "info")]
async fn upgrade_room_create_legacy(
services: &Services,
sender_user: &UserId,
old_room_id: &RoomId,
new_version: &RoomVersionId,
version_rules: &RoomVersionRules,
predecessor: PreviousRoom,
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
// Create a replacement room
let new_room_id = RoomId::new_v1(services.globals.server_name());
let state_lock = services.state.mutex.lock(&new_room_id).await;
let _short_id = services
.short
.get_or_create_shortroomid(&new_room_id)
.await;
// Get the old room creation event
let mut content: CanonicalJsonObject = services
.state_accessor
.room_state_get_content(old_room_id, &StateEventType::RoomCreate, "")
.await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
// Send a m.room.create event containing a predecessor field and the applicable
// room_version. "creator" key no longer exists in V11+ rooms.
{
use RoomVersionId::*;
match new_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
content.insert("creator".into(), json!(&sender_user).try_into()?),
| _ => content.remove("creator"),
}
};
content.insert("predecessor".into(), json!(predecessor).try_into()?);
content.insert("room_version".into(), json!(new_version).try_into()?);
// Validate creation event content
if serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&create_event_content)?.get())
.is_err()
{
return Err!(Request(BadJson("Error forming creation event")));
let raw_content = to_raw_value(&content)?;
if let Err(e) = serde_json::from_str::<CanonicalJsonObject>(raw_content.get()) {
return Err!(Request(BadJson("Error forming creation event: {e}")));
}
services
@@ -165,125 +296,229 @@ pub(crate) async fn upgrade_room_route(
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content)?,
unsigned: None,
content: to_raw_value(&content)?,
state_key: Some(StateKey::new()),
redacts: None,
timestamp: None,
..Default::default()
},
sender_user,
&replacement_room,
&new_room_id,
&state_lock,
)
.await?;
// Join the new room
services
Ok((new_room_id, state_lock))
}
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn transfer_room(&self) -> Result {
self.move_joined_member().await?;
self.move_state_events().await?;
self.move_local_aliases().await?;
self.tombstone_old_room().await?;
// After commitment to the tombstone above no more errors can propagate.
self.lockdown_old_room()
.await
.inspect_err(|e| error!(?self, "Failed to lockdown old room: {e}"))
.ok();
Ok(())
}
// Join the new room
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn move_joined_member(&self) -> Result<OwnedEventId> {
let old_content: RoomMemberEventContent = self
.services
.state_accessor
.room_state_get_content(
self.old_room_id,
&StateEventType::RoomMember,
self.sender_user.as_str(),
)
.inspect_err(|e| error!(?self, "Missing room member event: {e}"))
.await?;
self.services
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None,
join_authorized_via_users_server: None,
})?,
unsigned: None,
state_key: Some(sender_user.as_str().into()),
redacts: None,
timestamp: None,
},
sender_user,
&replacement_room,
&state_lock,
PduBuilder::state(self.sender_user.as_str(), &RoomMemberEventContent {
membership: MembershipState::Join,
..old_content
}),
self.sender_user,
self.new_room_id,
self.new_state_lock,
)
.await?;
.await
}
// Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS {
let event_content = match services
.state_accessor
.room_state_get(&body.room_id, event_type, "")
.await
// Replicate transferable state events to the new room
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn move_state_events(&self) -> Result {
RECOMMENDED_TRANSFERABLE_STATE_EVENT_TYPES
.iter()
.rev()
.stream()
.wide_filter_map(|event_type| {
self.services
.state_accessor
.room_state_get(self.old_room_id, event_type, "")
.ok()
})
.map(Ok)
.try_for_each(async |event| {
self.services
.timeline
.build_and_append_pdu(
self.rebuild_state_event(&event)?,
self.sender_user,
self.new_room_id,
self.new_state_lock,
)
.inspect_err(|e| {
error!(?event, ?self, "Failed to transfer state on upgrade: {e}");
})
.map_ok(|_| ())
.await
})
.await
}
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
fn rebuild_state_event<Pdu: Event>(&self, event: &Pdu) -> Result<PduBuilder> {
let content = match event.kind() {
| TimelineEventType::RoomPowerLevels
if self
.new_version_rules
.authorization
.explicitly_privilege_room_creators =>
{
| Ok(v) => v.content().to_owned(),
| Err(_) => continue, // Skipping missing events.
};
let mut content = event.get_content_as_value();
services
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: event_type.to_string().into(),
content: event_content,
state_key: Some(StateKey::new()),
..Default::default()
},
sender_user,
&replacement_room,
&state_lock,
)
.await?;
}
if let Some(users) = content
.get_mut("users")
.and_then(JsonValue::as_object_mut)
{
users.retain(|user_id, _pl| {
!self
.additional_creators
.iter()
.map(AsRef::as_ref)
.map(UserId::as_str)
.any(is_equal_to!(user_id.as_str()))
&& self.sender_user.as_str() != user_id.as_str()
});
}
// Moves any local aliases to the new room
let mut local_aliases = services
if content["events"]["m.room.tombstone"]
.as_i64()
.is_none_or(is_less_than!(150))
{
content["events"]["m.room.tombstone"] = to_value(150)?;
}
to_raw_value(&content)?
},
| _ => to_raw_value(event.content())?,
};
Ok(PduBuilder {
content,
event_type: event.kind().clone(),
state_key: event.state_key().map(Into::into),
..Default::default()
})
}
// Moves any local aliases to the new room
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn move_local_aliases(&self) -> Result {
self.services
.alias
.local_aliases_for_room(&body.room_id)
.boxed();
.local_aliases_for_room(self.old_room_id)
.filter_map(|alias| {
self.services
.alias
.remove_alias(alias, self.sender_user)
.inspect_err(move |e| error!(?alias, ?self, "Failed to remove alias: {e}"))
.map_ok(move |()| alias)
.ok()
})
.ready_for_each(|alias| {
self.services
.alias
.set_alias(alias, self.new_room_id, self.sender_user)
.inspect_err(|e| error!(?self, "Failed to add alias: {e}"))
.ok();
})
.map(Ok)
.await
}
while let Some(alias) = local_aliases.next().await {
services
.alias
.remove_alias(alias, sender_user)
.await?;
services
.alias
.set_alias(alias, &replacement_room, sender_user)?;
}
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
// permissions.
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn tombstone_old_room(&self) -> Result<OwnedEventId> {
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been upgraded.".to_owned(),
replacement_room: self.new_room_id.to_owned(),
}),
self.sender_user,
self.old_room_id,
self.old_state_lock,
)
.await
}
// Modify the power levels in the old room to prevent sending of events and
// inviting new users. Though a Result is returned, the callsite above treats it
// as infallible because the tombstone represents the commitment.
#[implement(RoomUpgradeContext, params = "<'_>")]
#[tracing::instrument(level = "debug")]
async fn lockdown_old_room(&self) -> Result<OwnedEventId> {
// Get the old room power levels
let power_levels_event_content: RoomPowerLevelsEventContent = services
let old_content: RoomPowerLevelsEventContent = self
.services
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomPowerLevels, "")
.room_state_get_content(self.old_room_id, &StateEventType::RoomPowerLevels, "")
.await
.map_err(|_| err!(Database("Found room without m.room.power_levels event.")))?;
// Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max(
int!(50),
power_levels_event_content
.users_default
.checked_add(int!(1))
.ok_or_else(|| {
err!(Request(BadJson("users_default power levels event content is not valid")))
})?,
);
let old_users_default = old_content
.users_default
.checked_add(int!(1))
.ok_or_else(|| {
err!(Request(BadJson("users_default power levels event content is not valid")))
})?;
// Modify the power levels in the old room to prevent sending of events and
// inviting new users
services
// Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max(int!(50), old_users_default);
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
events_default: new_level,
invite: new_level,
..power_levels_event_content
..old_content
}),
sender_user,
&body.room_id,
&state_lock,
self.sender_user,
self.old_room_id,
self.old_state_lock,
)
.await?;
drop(state_lock);
// Return the replacement room id
Ok(upgrade_room::v3::Response { replacement_room })
.await
}
+90 -55
View File
@@ -35,11 +35,11 @@
};
use tokio::time;
use tuwunel_core::{
Error, Result, at,
Result, at,
debug::INFO_SPAN_LEVEL,
err,
error::{inspect_debug_log, inspect_log},
extract_variant, is_equal_to,
extract_variant, is_equal_to, is_false, is_true,
matrix::{
Event,
event::Matches,
@@ -50,7 +50,7 @@
trace,
utils::{
self, BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::{OptionStream, ReadyEqExt},
future::{OptionStream, ReadyBoolExt},
math::ruma_from_u64,
result::MapExpect,
stream::{BroadbandExt, Tools, TryExpect, WidebandExt},
@@ -539,7 +539,7 @@ async fn handle_left_room(
return Ok(None);
}
let is_not_found = services.metadata.exists(room_id).eq(&false);
let is_not_found = services.metadata.exists(room_id).is_false();
let is_disabled = services.metadata.is_disabled(room_id);
@@ -744,8 +744,10 @@ async fn load_joined_room(
.room
.timeline
.limit
.unwrap_or_else(|| uint!(10))
.try_into()?;
.map(TryInto::try_into)
.map_expect("UInt to usize")
.unwrap_or(10)
.min(100);
let (timeline_pdus, limited, last_timeline_count) = load_timeline(
services,
@@ -757,10 +759,20 @@ async fn load_joined_room(
)
.await?;
let since_shortstatehash = services
.timeline
.prev_shortstatehash(room_id, PduCount::Normal(since).saturating_add(1))
.ok();
let timeline_changed = last_timeline_count.into_unsigned() > since;
debug_assert!(
timeline_pdus.is_empty() || timeline_changed,
"if timeline events, last_timeline_count must be in the since window."
);
let since_shortstatehash: OptionFuture<_> = timeline_changed
.then(|| {
services
.timeline
.prev_shortstatehash(room_id, PduCount::Normal(since).saturating_add(1))
.ok()
})
.into();
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
.first()
@@ -773,12 +785,20 @@ async fn load_joined_room(
})
.into();
let current_shortstatehash = services
.timeline
.get_shortstatehash(room_id, last_timeline_count)
.inspect_err(inspect_debug_log)
.or_else(|_| services.state.get_room_shortstatehash(room_id))
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))));
let current_shortstatehash: OptionFuture<_> = timeline_changed
.then(|| {
services
.timeline
.get_shortstatehash(room_id, last_timeline_count)
.inspect_err(inspect_debug_log)
.or_else(|_| services.state.get_room_shortstatehash(room_id))
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))
})
.into();
let encrypted_room: OptionFuture<_> = timeline_changed
.then(|| services.state_accessor.is_encrypted_room(room_id))
.into();
let receipt_events = services
.read_receipt
@@ -792,8 +812,6 @@ async fn load_joined_room(
})
.collect::<HashMap<OwnedUserId, Raw<AnySyncEphemeralRoomEvent>>>();
let encrypted_room = services.state_accessor.is_encrypted_room(room_id);
let (
(since_shortstatehash, horizon_shortstatehash, current_shortstatehash),
receipt_events,
@@ -803,8 +821,12 @@ async fn load_joined_room(
receipt_events,
encrypted_room,
)
.map(|((since, horizon, current), receipt, encrypted_room)| {
Ok::<_, Error>(((since, horizon.flat_ok(), current?), receipt, encrypted_room))
.map(|((since, horizon, current), receipt, encrypted_room)| -> Result<_> {
Ok((
(since.flatten(), horizon.flat_ok(), current.transpose()?),
receipt,
encrypted_room,
))
})
.boxed()
.await?;
@@ -812,7 +834,7 @@ async fn load_joined_room(
let lazy_load_options =
[&filter.room.state.lazy_load_options, &filter.room.timeline.lazy_load_options];
let lazy_loading_enabled = !encrypted_room
let lazy_loading_enabled = encrypted_room.is_some_and(is_false!())
&& lazy_load_options
.iter()
.any(|opts| opts.is_enabled());
@@ -847,9 +869,14 @@ async fn load_joined_room(
})
.into();
let sender_joined_count = services
.state_cache
.get_joined_count(room_id, sender_user);
let sender_joined_count: OptionFuture<_> = timeline_changed
.then(|| {
services
.state_cache
.get_joined_count(room_id, sender_user)
.unwrap_or(0)
})
.into();
let since_encryption: OptionFuture<_> = since_shortstatehash
.map(|shortstatehash| {
@@ -859,10 +886,6 @@ async fn load_joined_room(
})
.into();
let last_privateread_update = services
.read_receipt
.last_privateread_update(sender_user, room_id);
let last_notification_read: OptionFuture<_> = timeline_pdus
.is_empty()
.then(|| {
@@ -873,6 +896,10 @@ async fn load_joined_room(
})
.into();
let last_privateread_update = services
.read_receipt
.last_privateread_update(sender_user, room_id);
let (
(last_privateread_update, last_notification_read),
(sender_joined_count, since_encryption),
@@ -884,27 +911,36 @@ async fn load_joined_room(
)
.await;
let _encrypted_since_last_sync = !initial && encrypted_room && since_encryption.is_none();
let _encrypted_since_last_sync =
!initial && encrypted_room.is_some_and(is_true!()) && since_encryption.is_none();
let joined_since_last_sync = sender_joined_count.is_ok_and(|count| count > since);
let joined_since_last_sync = sender_joined_count.unwrap_or(0) > since;
let state_changes: OptionFuture<_> = current_shortstatehash
.map(|current_shortstatehash| {
calculate_state_changes(
services,
sender_user,
room_id,
full_state || initial,
since_shortstatehash,
horizon_shortstatehash,
current_shortstatehash,
joined_since_last_sync,
witness.as_ref(),
)
})
.into();
let StateChanges {
heroes,
joined_member_count,
invited_member_count,
mut state_events,
} = calculate_state_changes(
services,
sender_user,
room_id,
full_state || initial,
since_shortstatehash,
horizon_shortstatehash,
current_shortstatehash,
joined_since_last_sync,
witness.as_ref(),
)
.await?;
} = state_changes
.await
.transpose()?
.unwrap_or_default();
let is_sender_membership = |event: &PduEvent| {
*event.event_type() == StateEventType::RoomMember.into()
@@ -1182,6 +1218,7 @@ async fn calculate_state_changes<'a>(
.state_accessor
.state_full_shortids(horizon_shortstatehash)
.expect_ok()
.boxed()
.into_future()
})
.into();
@@ -1262,7 +1299,10 @@ async fn calculate_counts(
let small_room = joined_member_count.saturating_add(invited_member_count) <= 5;
let heroes: OptionFuture<_> = small_room
let heroes: OptionFuture<_> = services
.config
.calculate_heroes
.and_is(small_room)
.then(|| calculate_heroes(services, room_id, sender_user))
.into();
@@ -1275,28 +1315,23 @@ async fn calculate_heroes(
sender_user: &UserId,
) -> Vec<OwnedUserId> {
services
.timeline
.all_pdus(sender_user, room_id)
.ready_filter(|(_, pdu)| pdu.kind == RoomMember)
.fold_default(|heroes: Vec<_>, (_, pdu)| {
.state_accessor
.room_state_type_pdus(room_id, &StateEventType::RoomMember)
.ready_filter_map(Result::ok)
.fold_default(|heroes: Vec<_>, pdu| {
fold_hero(heroes, services, room_id, sender_user, pdu)
})
.await
}
async fn fold_hero(
async fn fold_hero<Pdu: Event>(
mut heroes: Vec<OwnedUserId>,
services: &Services,
room_id: &RoomId,
sender_user: &UserId,
pdu: PduEvent,
pdu: Pdu,
) -> Vec<OwnedUserId> {
let Some(user_id): Option<&UserId> = pdu
.state_key
.as_deref()
.map(TryInto::try_into)
.flat_ok()
else {
let Some(user_id): Option<&UserId> = pdu.state_key().map(TryInto::try_into).flat_ok() else {
return heroes;
};
@@ -170,6 +170,7 @@ async fn collect_room(
.ready_filter(|&user_id| user_id != sender_user)
.map(ToOwned::to_owned)
.map(|user_id| (MembershipState::Join, user_id))
.boxed()
.into_future()
})
.into();
+4 -4
View File
@@ -7,7 +7,7 @@
is_equal_to, is_true,
utils::{
BoolExt, FutureBoolExt, IterStream, ReadyExt,
future::{self, OptionExt, ReadyEqExt},
future::{self, OptionExt, ReadyBoolExt},
},
};
@@ -132,7 +132,7 @@ pub(super) async fn filter_room_meta(
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
room_id: &RoomId,
) -> bool {
let not_exists = services.metadata.exists(room_id).eq(&false);
let not_exists = services.metadata.exists(room_id).is_false();
let is_disabled = services.metadata.is_disabled(room_id);
@@ -141,13 +141,13 @@ pub(super) async fn filter_room_meta(
let not_visible = services
.state_accessor
.user_can_see_state_events(sender_user, room_id)
.eq(&false);
.is_false();
pin_mut!(not_visible, not_exists, is_disabled, is_banned);
not_visible
.or(not_exists)
.or(is_disabled)
.or(is_banned)
.is_false()
.await
.eq(&false)
}
+19 -11
View File
@@ -327,20 +327,27 @@ async fn handle_room(
.boxed()
.await;
let (heroes, hero_name, heroes_avatar) = calculate_heroes(
services,
sender_user,
room_id,
room_name.as_ref(),
room_avatar.as_deref(),
)
.await?;
let heroes: OptionFuture<_> = services
.config
.calculate_heroes
.then(|| {
calculate_heroes(
services,
sender_user,
room_id,
room_name.as_ref(),
room_avatar.as_deref(),
)
})
.into();
let (heroes, heroes_name, heroes_avatar) = heroes.await.unwrap_or_default();
Ok(response::Room {
initial: roomsince.eq(&0).then_some(true),
lists: lists.clone(),
membership: membership.clone(),
name: room_name.or(hero_name),
name: room_name.or(heroes_name),
avatar: JsOption::from_option(room_avatar.or(heroes_avatar)),
is_dm,
heroes,
@@ -365,8 +372,9 @@ async fn calculate_heroes(
room_id: &RoomId,
room_name: Option<&DisplayName>,
room_avatar: Option<&MxcUri>,
) -> Result<(Option<Heroes>, Option<DisplayName>, Option<OwnedMxcUri>)> {
) -> (Option<Heroes>, Option<DisplayName>, Option<OwnedMxcUri>) {
const MAX_HEROES: usize = 5;
let heroes: Heroes = services
.state_cache
.room_members(room_id)
@@ -443,5 +451,5 @@ async fn calculate_heroes(
})
.flatten();
Ok((Some(heroes), hero_name, heroes_avatar))
(Some(heroes), hero_name, heroes_avatar)
}
+4 -4
View File
@@ -243,8 +243,8 @@ pub(crate) async fn get_timezone_key_route(
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
.federation
.execute(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
@@ -304,8 +304,8 @@ pub(crate) async fn get_profile_field_route(
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
.federation
.execute(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
+2 -1
View File
@@ -37,7 +37,8 @@ pub(crate) async fn get_supported_versions_route(
"v1.3".to_owned(),
"v1.4".to_owned(),
"v1.5".to_owned(),
"v1.11".to_owned(),
"v1.10".to_owned(), // relations recursion
"v1.11".to_owned(), // authenticated media
],
unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true),
+2 -1
View File
@@ -34,7 +34,7 @@ pub(crate) async fn search_users_route(
.min(LIMIT_MAX);
let search_term = body.search_term.to_lowercase();
let mut users = services
let users = services
.users
.stream()
.ready_filter(|&user_id| user_id != sender_user)
@@ -83,6 +83,7 @@ pub(crate) async fn search_users_route(
})
});
pin_mut!(users);
let results = users.by_ref().take(limit).collect().await;
let limited = users.next().await.is_some();
+1 -1
View File
@@ -87,7 +87,7 @@ impl<T> FromRequest<State, Body> for Args<T>
level = "debug",
skip(services),
err(level = "debug")
ret,
ret(level = "trace"),
)]
async fn from_request(
request: hyper::Request<Body>,
+5 -4
View File
@@ -1,5 +1,6 @@
use axum::RequestPartsExt;
use axum_extra::{TypedHeader, headers::Authorization, typed_header::TypedHeaderRejectionReason};
use http::uri::PathAndQuery;
use ruma::{
CanonicalJsonName, CanonicalJsonObject, CanonicalJsonValue,
api::federation::authentication::XMatrix,
@@ -30,8 +31,8 @@ pub(super) async fn auth_server(
.parts
.uri
.path_and_query()
.expect("all requests have a path")
.to_string();
.map(PathAndQuery::as_str)
.unwrap_or("/");
let signature: [Member; 1] =
[(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
@@ -45,7 +46,7 @@ pub(super) async fn auth_server(
("method".into(), Value::String(request.parts.method.as_str().into())),
("origin".into(), Value::String(origin.as_str().into())),
("signatures".into(), Value::Object(signatures.into())),
("uri".into(), Value::String(signature_uri)),
("uri".into(), Value::String(signature_uri.into())),
];
authorization.into()
@@ -55,7 +56,7 @@ pub(super) async fn auth_server(
("method".into(), Value::String(request.parts.method.as_str().into())),
("origin".into(), Value::String(origin.as_str().into())),
("signatures".into(), Value::Object(signatures.into())),
("uri".into(), Value::String(signature_uri)),
("uri".into(), Value::String(signature_uri.into())),
];
authorization.into()
+1 -1
View File
@@ -2,7 +2,7 @@
use tuwunel_service::Services;
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct State {
services: *const Services,
}
+2 -2
View File
@@ -198,8 +198,8 @@ pub(crate) async fn create_invite_route(
for appservice in services.appservice.read().await.values() {
if appservice.is_user_match(&invited_user) {
services
.sending
.send_appservice_request(
.appservice
.send_request(
appservice.registration.clone(),
ruma::api::appservice::event::push_events::v1::Request {
events: vec![pdu.to_format()],
+1
View File
@@ -208,6 +208,7 @@ async fn create_join_event(
.handle_incoming_pdu(&origin, room_id, &event_id, value.clone(), true)
.boxed()
.await?
.map(at!(0))
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
+2 -1
View File
@@ -11,7 +11,7 @@
serde::JsonObject,
};
use tuwunel_core::{
Err, Result, err,
Err, Result, at, err,
matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
warn,
};
@@ -170,6 +170,7 @@ pub(crate) async fn create_knock_event_v1_route(
.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
.boxed()
.await?
.map(at!(0))
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
+2 -1
View File
@@ -11,7 +11,7 @@
},
};
use serde_json::value::RawValue as RawJsonValue;
use tuwunel_core::{Err, Result, err, matrix::event::gen_event_id_canonical_json};
use tuwunel_core::{Err, Result, at, err, matrix::event::gen_event_id_canonical_json};
use tuwunel_service::Services;
use crate::Ruma;
@@ -151,6 +151,7 @@ async fn create_leave_event(
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
.boxed()
.await?
.map(at!(0))
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
+5 -13
View File
@@ -26,18 +26,14 @@ gzip_compression = [
"reqwest/gzip",
]
jemalloc = [
"dep:tikv-jemalloc-sys",
"dep:tikv-jemalloc-ctl",
"dep:tikv-jemallocator",
"dep:jevmalloc",
]
jemalloc_conf = []
jemalloc_prof = [
"tikv-jemalloc-sys/profiling",
#"jevmalloc/profiling",
]
jemalloc_stats = [
"tikv-jemalloc-sys/stats",
"tikv-jemalloc-ctl/stats",
"tikv-jemallocator/stats",
#"jevmalloc/stats",
]
perf_measurements = []
release_max_log_level = [
@@ -96,12 +92,8 @@ serde.workspace = true
smallvec.workspace = true
smallstr.workspace = true
thiserror.workspace = true
tikv-jemallocator.optional = true
tikv-jemallocator.workspace = true
tikv-jemalloc-ctl.optional = true
tikv-jemalloc-ctl.workspace = true
tikv-jemalloc-sys.optional = true
tikv-jemalloc-sys.workspace = true
jevmalloc.optional = true
jevmalloc.workspace = true
tokio.workspace = true
tokio-metrics.workspace = true
toml.workspace = true
+78 -23
View File
@@ -1,20 +1,26 @@
//! jemalloc allocator
use std::{
alloc::Layout,
cell::OnceCell,
ffi::{CStr, c_char, c_void},
fmt::Debug,
sync::RwLock,
panic::catch_unwind,
process::abort,
sync::{
Mutex,
atomic::{AtomicBool, AtomicU64, Ordering},
},
};
use arrayvec::ArrayVec;
use tikv_jemalloc_ctl as mallctl;
use tikv_jemalloc_sys as ffi;
use tikv_jemallocator as jemalloc;
use jevmalloc as jemalloc;
use jevmalloc::{ctl as mallctl, ffi};
use crate::{
Result, err, is_equal_to, is_nonzero,
utils::{math, math::Tried},
Result,
arrayvec::ArrayVec,
err, is_equal_to, is_nonzero,
utils::{BoolExt, math, math::Tried},
};
#[cfg(feature = "jemalloc_conf")]
@@ -29,7 +35,7 @@
",metadata_thp:always",
",background_thread:true",
",max_background_threads:-1",
MALLOC_CONF_PROF,
//MALLOC_CONF_PROF,
0
);
@@ -38,16 +44,12 @@
feature = "jemalloc_prof",
target_arch = "x86_64",
))]
const MALLOC_CONF_PROF: &str = ",prof_active:false";
const _MALLOC_CONF_PROF: &str = ",prof_active:false";
#[cfg(all(
feature = "jemalloc_conf",
any(not(feature = "jemalloc_prof"), not(target_arch = "x86_64")),
))]
const MALLOC_CONF_PROF: &str = "";
#[global_allocator]
static JEMALLOC: jemalloc::Jemalloc = jemalloc::Jemalloc;
static CONTROL: RwLock<()> = RwLock::new(());
const _MALLOC_CONF_PROF: &str = "";
type Name = ArrayVec<u8, NAME_MAX>;
type Key = ArrayVec<usize, KEY_SEGS>;
@@ -55,14 +57,27 @@
const NAME_MAX: usize = 128;
const KEY_SEGS: usize = 8;
#[global_allocator]
static JEMALLOC: jemalloc::Jemalloc = jemalloc::Jemalloc;
static CONTROL: Mutex<()> = Mutex::new(());
static GLOBAL_ALLOCS: AtomicU64 = AtomicU64::new(0);
static COUNT_GLOBAL_ALLOCS: AtomicBool = AtomicBool::new(false);
static TRACE_GLOBAL_ALLOCS: AtomicBool = AtomicBool::new(false);
#[crate::ctor]
fn _static_initialization() {
acq_epoch().expect("pre-initialization of jemalloc failed");
acq_epoch().expect("pre-initialization of jemalloc failed");
// SAFETY: Mutable static globals in jemalloc crate; must be initialized
// properly and uniquely.
unsafe {
jemalloc::hook::ALLOC = Some(global_alloc_hook);
jemalloc::hook::ALLOC_ZEROED = Some(global_alloc_zeroed_hook);
};
}
#[must_use]
#[cfg(feature = "jemalloc_stats")]
#[cfg(disable)]
//#[cfg(feature = "jemalloc_stats")]
pub fn memory_usage() -> Option<String> {
use mallctl::stats;
@@ -91,7 +106,7 @@ pub fn memory_usage() -> Option<String> {
}
#[must_use]
#[cfg(not(feature = "jemalloc_stats"))]
//#[cfg(not(feature = "jemalloc_stats"))]
pub fn memory_usage() -> Option<String> { None }
pub fn memory_stats(opts: &str) -> Option<String> {
@@ -117,6 +132,12 @@ pub fn memory_stats(opts: &str) -> Option<String> {
}
unsafe extern "C" fn malloc_stats_cb(opaque: *mut c_void, msg: *const c_char) {
catch_unwind(move || handle_malloc_stats(opaque, msg))
.map_err(|_| abort())
.ok();
}
fn handle_malloc_stats(opaque: *mut c_void, msg: *const c_char) {
// SAFETY: we have to trust the opaque points to our String
let res: &mut String = unsafe {
opaque
@@ -132,6 +153,43 @@ pub fn memory_stats(opts: &str) -> Option<String> {
res.push_str(msg.as_ref());
}
fn global_alloc_hook(layout: Layout) {
catch_unwind(move || handle_global_alloc(layout))
.map_err(|_| abort())
.ok();
}
fn global_alloc_zeroed_hook(layout: Layout) {
catch_unwind(move || handle_global_alloc(layout))
.map_err(|_| abort())
.ok();
}
fn handle_global_alloc(layout: Layout) {
use std::io::Write;
use libc::{STDOUT_FILENO, write};
let do_count = COUNT_GLOBAL_ALLOCS.load(Ordering::Relaxed);
let count = GLOBAL_ALLOCS.fetch_add(do_count.into(), Ordering::Relaxed);
if TRACE_GLOBAL_ALLOCS.load(Ordering::Relaxed) {
let mut buf = ArrayVec::<u8, 128>::new();
writeln!(&mut buf, "{count} align={} size={}", layout.align(), layout.size())
.expect("writeln! to buffer failed");
// SAFETY: Valid ptr and len from buf for writing to stdout.
unsafe { write(STDOUT_FILENO, buf.as_ptr().cast::<c_void>(), buf.len()) }
.ge(&0)
.into_result()
.expect("write(2) error");
}
}
#[inline]
#[must_use]
pub fn global_alloc_count() -> u64 { GLOBAL_ALLOCS.load(Ordering::Relaxed) }
macro_rules! mallctl {
($name:expr_2021) => {{
thread_local! {
@@ -339,7 +397,7 @@ fn set<T>(key: &Key, val: T) -> Result<T>
where
T: Copy + Debug,
{
let _lock = CONTROL.write()?;
let _lock = CONTROL.lock()?;
let res = xchg(key, val)?;
inc_epoch()?;
@@ -357,7 +415,6 @@ fn get<T>(key: &Key) -> Result<T>
T: Copy + Debug,
{
acq_epoch()?;
acq_epoch()?;
// SAFETY: T must be perfectly valid to receive value.
unsafe { mallctl::raw::read_mib(key.as_slice()) }.map_err(map_err)
@@ -401,6 +458,4 @@ fn name(name: &str) -> Result<Name> {
Ok(buf)
}
fn map_err(error: tikv_jemalloc_ctl::Error) -> crate::Error {
err!("mallctl: {}", error.to_string())
}
fn map_err(error: jemalloc::ctl::Error) -> crate::Error { err!("mallctl: {}", error.to_string()) }
+66
View File
@@ -912,6 +912,26 @@ pub struct Config {
#[serde(default)]
pub log_to_stderr: bool,
/// Setting to false disables the logging/tracing system at a lower level.
/// In contrast to configuring an empty `log` string where the system is
/// still operating but muted, when this option is false the system was not
/// initialized and is not operating. Changing this option has no effect
/// after startup. This option is intended for developers and expert use
/// only: configuring an empty log string is preferred over using this.
///
/// default: true
#[serde(default = "true_fn")]
pub log_enable: bool,
/// Setting to false disables the logging/tracing system at a lower level
/// similar to `log_enable`. In this case the system is configured normally,
/// but not registered as the global handler in the final steps. This option
/// is for developers and expert use only.
///
/// default: true
#[serde(default = "true_fn")]
pub log_global_default: bool,
/// OpenID token expiration/TTL in seconds.
///
/// These are the OpenID tokens that are primarily used for Matrix account
@@ -1336,6 +1356,17 @@ pub struct Config {
#[serde(default)]
pub push_everything: bool,
/// Setting to false disables the heroes calculation made by sliding and
/// legacy client sync. The heroes calculation is mandated by the Matrix
/// specification and your client may not operate properly unless this
/// option is set to true.
///
/// This option is intended for custom software deployments seeking purely
/// to minimize unused resources; the overall savings are otherwise
/// negligible.
#[serde(default = "true_fn")]
pub calculate_heroes: bool,
/// Allow local (your server only) presence updates/requests.
///
/// Note that presence on tuwunel is very fast unlike Synapse's. If using
@@ -1726,6 +1757,26 @@ pub struct Config {
)]
pub deprioritize_joins_through_servers: RegexSet,
/// Maximum make_join requests to attempt within each join attempt. Each
/// attempt tries a different server, as each server is only tried once;
/// though retries can occur when the join request as a whole is retried.
///
/// default: 48
#[serde(default = "default_max_make_join_attempts_per_join_attempt")]
pub max_make_join_attempts_per_join_attempt: usize,
/// Maximum join attempts to conduct per client join request. Each join
/// attempt consists of one or more make_join requests limited above, and a
/// single send_join request. This value allows for additional servers to
/// act as the join-server prior to reporting the last error back to the
/// client, which can be frustrating for users. Therefor the default value
/// is greater than one, but less than excessively exceeding the client's
/// request timeout, though that may not be avoidable in some cases.
///
/// default: 3
#[serde(default = "default_max_join_attempts_per_join_request")]
pub max_join_attempts_per_join_request: usize,
/// Retry failed and incomplete messages to remote servers immediately upon
/// startup. This is called bursting. If this is disabled, said messages may
/// not be delivered until more messages are queued for that server. Do not
@@ -2033,6 +2084,17 @@ pub struct Config {
#[serde(default)]
pub allow_invalid_tls_certificates: bool,
/// Sets the `Access-Control-Allow-Origin` header included by this server in
/// all responses. A list of multiple values can be specified. The default
/// is an empty list. The actual header defaults to `*` upon an empty list.
///
/// There is no reason to configure this without specific intent. Incorrect
/// values may degrade or disrupt clients.
///
/// default: []
#[serde(default)]
pub access_control_allow_origin: BTreeSet<String>,
/// Backport state-reset security fixes to all room versions.
///
/// This option applies the State Resolution 2.1 mitigation developed during
@@ -2955,3 +3017,7 @@ fn default_deprioritize_joins_through_servers() -> RegexSet {
}
fn default_one_time_key_limit() -> usize { 256 }
fn default_max_make_join_attempts_per_join_attempt() -> usize { 48 }
fn default_max_join_attempts_per_join_request() -> usize { 3 }
+1 -1
View File
@@ -95,7 +95,7 @@ pub fn trap() {
}
#[must_use]
pub fn panic_str(p: &Box<dyn Any + Send>) -> &'static str {
pub fn panic_str(p: &Box<dyn Any + Send + 'static>) -> &'static str {
(**p)
.downcast_ref::<&str>()
.copied()
+27 -11
View File
@@ -4,16 +4,22 @@
mod response;
mod serde;
use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError};
use std::{
any::Any,
borrow::Cow,
convert::Infallible,
sync::{Mutex, PoisonError},
};
pub use self::{err::visit, log::*};
use crate::utils::{assert_ref_unwind_safe, assert_send, assert_sync, assert_unwind_safe};
#[derive(thiserror::Error)]
pub enum Error {
#[error("PANIC!")]
PanicAny(Box<dyn Any + Send>),
PanicAny(Mutex<Box<dyn Any + Send>>),
#[error("PANIC! {0}")]
Panic(&'static str, Box<dyn Any + Send + 'static>),
Panic(&'static str, Mutex<Box<dyn Any + Send + 'static>>),
// std
#[error(transparent)]
@@ -27,7 +33,7 @@ pub enum Error {
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error(transparent)]
Std(#[from] Box<dyn std::error::Error + Send>),
Std(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)]
SystemTime(#[from] std::time::SystemTimeError),
#[error(transparent)]
@@ -136,6 +142,11 @@ pub enum Error {
Err(Cow<'static, str>),
}
static _IS_SEND: () = assert_send::<Error>();
static _IS_SYNC: () = assert_sync::<Error>();
static _IS_UNWIND_SAFE: () = assert_unwind_safe::<Error>();
static _IS_REF_UNWIND_SAFE: () = assert_ref_unwind_safe::<Error>();
impl Error {
#[inline]
#[must_use]
@@ -167,13 +178,14 @@ pub fn message(&self) -> String {
/// Returns the Matrix error code / error kind
#[inline]
pub fn kind(&self) -> ruma::api::client::error::ErrorKind {
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
use ruma::api::client::error::ErrorKind::{FeatureDisabled, NotJson, Unknown};
match self {
| Self::FeatureDisabled(..) => FeatureDisabled,
| Self::CanonicalJson(..) | Self::Json(..) => NotJson,
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
| Self::Federation(_, error) | Self::Ruma(error) =>
response::ruma_error_kind(error).clone(),
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
| Self::FeatureDisabled(..) => FeatureDisabled,
| _ => Unknown,
}
}
@@ -184,15 +196,19 @@ pub fn status_code(&self) -> http::StatusCode {
use http::StatusCode;
match self {
| Self::Conflict(_) => StatusCode::CONFLICT, // room alias exists
| Self::Federation(_, error) | Self::Ruma(error) => error.status_code,
| Self::Request(kind, _, code) => response::status_code(kind, *code),
| Self::FeatureDisabled(..)
| Self::CanonicalJson(..)
| Self::Json(..)
| Self::JsParseInt(..)
| Self::JsTryFromInt(..) => response::bad_request_code(&self.kind()),
| Self::BadRequest(kind, ..) => response::bad_request_code(kind),
| Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()),
| Self::Request(kind, _, code) => response::status_code(kind, *code),
| Self::Io(error) => response::io_error_code(error.kind()),
| Self::Reqwest(error) => error
.status()
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
| Self::Conflict(_) => StatusCode::CONFLICT,
| Self::Io(error) => response::io_error_code(error.kind()),
| _ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
+11 -6
View File
@@ -15,13 +15,16 @@ pub fn panic(self) -> ! { panic_any(self.into_panic()) }
#[must_use]
#[inline]
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) }
pub fn from_panic(e: Box<dyn Any + Send + 'static>) -> Self {
Self::Panic(debug::panic_str(&e), e.into())
}
#[inline]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
pub fn into_panic(self) -> Box<dyn Any + Send> {
match self {
| Self::Panic(_, e) | Self::PanicAny(e) => e,
| Self::JoinError(e) => e.into_panic(),
| Self::Panic(_, e) | Self::PanicAny(e) =>
e.into_inner().expect("Error contained panic"),
| _ => Box::new(self),
}
}
@@ -29,16 +32,18 @@ pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
/// Get the panic message string.
#[inline]
pub fn panic_str(self) -> Option<&'static str> {
self.is_panic()
.then_some(debug::panic_str(&self.into_panic()))
self.is_panic().then(|| {
let panic = self.into_panic();
debug::panic_str(&panic)
})
}
/// Check if the Error is trafficking a panic object.
#[inline]
pub fn is_panic(&self) -> bool {
match &self {
| Self::Panic(..) | Self::PanicAny(..) => true,
| Self::JoinError(e) => e.is_panic(),
| Self::Panic(..) | Self::PanicAny(..) => true,
| _ => false,
}
}
+2 -3
View File
@@ -5,11 +5,10 @@
use std::{
collections::BTreeMap,
mem::replace,
sync::{Mutex, OnceLock},
};
use crate::utils::exchange;
// Capture rustc version during compilation.
tuwunel_macros::rustc_version! {}
@@ -54,7 +53,7 @@ fn append_features(features: &mut Vec<&'static str>, flags: &[&'static str]) {
for flag in flags {
let is_cfg = *flag == "--cfg";
let is_feature = flag.starts_with("feature=");
if exchange(&mut next_is_cfg, is_cfg) && is_feature {
if replace(&mut next_is_cfg, is_cfg) && is_feature {
if let Some(feature) = flag
.split_once('=')
.map(|(_, feature)| feature.trim_matches('"'))
+15 -7
View File
@@ -8,23 +8,31 @@
mod reload;
mod suppress;
pub use capture::Capture;
pub use console::{ConsoleFormat, ConsoleWriter, is_systemd_mode};
pub use reload::{LogLevelReloadHandles, ReloadHandle};
pub use suppress::Suppress;
pub use tracing::Level;
use std::sync::Arc;
pub use tracing::{Level, subscriber::Subscriber};
pub use tracing_core::{Event, Metadata};
pub use tracing_subscriber::EnvFilter;
pub use self::{
capture::Capture,
console::{ConsoleFormat, ConsoleWriter, is_systemd_mode},
reload::{LogLevelReloadHandles, ReloadHandle},
suppress::Suppress,
};
/// Logging subsystem. This is a singleton member of super::Server which holds
/// all logging and tracing related state rather than shoving it all in
/// super::Server directly.
pub struct Log {
pub struct Logging {
/// Subscriber assigned to globals and defaults; may also be NoSubscriber.
pub subscriber: Arc<dyn Subscriber + Send + Sync>,
/// General log level reload handles.
pub reload: LogLevelReloadHandles,
/// Tracing capture state for ephemeral/oneshot uses.
pub capture: std::sync::Arc<capture::State>,
pub capture: Arc<capture::State>,
}
// Wraps for logging macros. Use these macros rather than extern tracing:: or
+114 -132
View File
@@ -1,4 +1,5 @@
use ruma::{
CanonicalJsonMemberOptional as JsonMember, CanonicalJsonMembersOptional as JsonMembers,
events::{
AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncMessageLikeEvent,
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent,
@@ -6,7 +7,7 @@
},
serde::Raw,
};
use serde_json::json;
use serde_json::value::to_raw_value;
use super::{Event, redact};
@@ -22,25 +23,20 @@ impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"type": event.event_type(),
});
let members: [JsonMember<_>; _] = [
("content", Some(content.into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("redacts", redacts.map(|e| e.as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -52,26 +48,21 @@ impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(content.into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("redacts", redacts.map(|e| e.as_str().into())),
("room_id", Some(event.room_id().as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -83,26 +74,21 @@ impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyMessageLikeEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(content.into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("redacts", redacts.map(|e| e.as_str().into())),
("room_id", Some(event.room_id().as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -114,25 +100,20 @@ impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncMessageLikeEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(content.into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("redacts", redacts.map(|e| e.as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -143,21 +124,20 @@ fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(event.content().into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("room_id", Some(event.room_id().as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -168,20 +148,19 @@ fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(event.content().into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -192,14 +171,16 @@ fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStrippedStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(event.content().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
];
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -210,15 +191,17 @@ fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
impl<'a, E: Event> From<Ref<'a, E>> for Raw<HierarchySpaceChildEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(event.content().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
];
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
@@ -229,21 +212,20 @@ fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
impl<'a, E: Event> From<Ref<'a, E>> for Raw<StateEvent<RoomMemberEventContent>> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"redacts": event.redacts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
let members: [JsonMember<_>; _] = [
("content", Some(event.content().into())),
("event_id", Some(event.event_id().as_str().into())),
("origin_server_ts", Some(event.origin_server_ts().get().into())),
("redacts", event.redacts().map(|e| e.as_str().into())),
("room_id", Some(event.room_id().as_str().into())),
("sender", Some(event.sender().as_str().into())),
("state_key", event.state_key().map(Into::into)),
("type", Some(event.event_type().to_string().into())),
("unsigned", event.unsigned().map(Into::into)),
];
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
to_raw_value(&JsonMembers(&members))
.map(Self::from_json)
.expect("Failed to serialize Event value")
}
}
+3 -3
View File
@@ -9,7 +9,7 @@
use ruma::OwnedServerName;
use tokio::{runtime, sync::broadcast};
use crate::{Err, Result, config, config::Config, log::Log, metrics::Metrics};
use crate::{Err, Result, config, config::Config, log::Logging, metrics::Metrics};
/// Server runtime state; public portion
pub struct Server {
@@ -41,7 +41,7 @@ pub struct Server {
pub signal: broadcast::Sender<&'static str>,
/// Logging subsystem state
pub log: Log,
pub log: Logging,
/// Metrics subsystem state
pub metrics: Metrics,
@@ -49,7 +49,7 @@ pub struct Server {
impl Server {
#[must_use]
pub fn new(config: Config, runtime: Option<runtime::Handle>, log: Log) -> Self {
pub fn new(config: Config, runtime: Option<runtime::Handle>, log: Logging) -> Self {
Self {
name: config.server_name.clone(),
config: config::Manager::new(config),
+10 -6
View File
@@ -2,12 +2,16 @@
mod ext_ext;
mod option_ext;
mod option_stream;
mod ready_bool_ext;
mod ready_eq_ext;
mod try_ext_ext;
pub use bool_ext::{BoolExt, and, and4, and5, and6, and7, or};
pub use ext_ext::ExtExt;
pub use option_ext::OptionExt;
pub use option_stream::OptionStream;
pub use ready_eq_ext::ReadyEqExt;
pub use try_ext_ext::TryExtExt;
pub use self::{
bool_ext::{BoolExt, and, and4, and5, and6, and7, or},
ext_ext::ExtExt,
option_ext::OptionExt,
option_stream::OptionStream,
ready_bool_ext::ReadyBoolExt,
ready_eq_ext::ReadyEqExt,
try_ext_ext::TryExtExt,
};
+18
View File
@@ -0,0 +1,18 @@
#![allow(clippy::wrong_self_convention)]
use futures::Future;
use super::ReadyEqExt;
pub trait ReadyBoolExt
where
Self: Future<Output = bool> + ReadyEqExt<bool> + Send,
{
#[inline]
fn is_false(self) -> impl Future<Output = bool> + Send { self.eq(&false) }
#[inline]
fn is_true(self) -> impl Future<Output = bool> + Send { self.eq(&true) }
}
impl<Fut> ReadyBoolExt for Fut where Fut: Future<Output = bool> + Send {}
+7 -2
View File
@@ -42,8 +42,13 @@
},
};
#[inline]
pub fn exchange<T>(state: &mut T, source: T) -> T { std::mem::replace(state, source) }
pub const fn assert_send<T: Send>() {}
pub const fn assert_sync<T: Sync>() {}
pub const fn assert_dst<T: ?Sized>() {}
pub const fn assert_sized<T: Sized>() {}
pub const fn assert_unpin<T: Unpin>() {}
pub const fn assert_unwind_safe<T: std::panic::UnwindSafe>() {}
pub const fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
#[macro_export]
macro_rules! extract_variant {
+2
View File
@@ -9,10 +9,12 @@
use crate::{Result, err};
/// Map of Mutexes
#[derive(Debug)]
pub struct MutexMap<Key, Val> {
map: Map<Key, Val>,
}
#[derive(Debug)]
pub struct Guard<Key, Val> {
map: Map<Key, Val>,
val: Omg<Val>,
+6 -4
View File
@@ -57,9 +57,10 @@ pub fn intersection_sorted<Item, Iter, Iters>(
/// Intersection of sets
///
/// Outputs the set of elements common to both streams. Streams must be sorted.
pub fn intersection_sorted_stream2<Item, S>(a: S, b: S) -> impl Stream<Item = Item> + Send
pub fn intersection_sorted_stream2<Item, A, B>(a: A, b: B) -> impl Stream<Item = Item> + Send
where
S: Stream<Item = Item> + Send + Unpin,
A: Stream<Item = Item> + Send,
B: Stream<Item = Item> + Send + Unpin,
Item: Eq + PartialOrd + Send + Sync,
{
use tokio::sync::Mutex;
@@ -86,9 +87,10 @@ pub fn intersection_sorted_stream2<Item, S>(a: S, b: S) -> impl Stream<Item = It
///
/// Outputs the set of elements found in `a` which are not found in `b`. Streams
/// must be sorted.
pub fn difference_sorted_stream2<Item, S>(a: S, b: S) -> impl Stream<Item = Item> + Send
pub fn difference_sorted_stream2<Item, A, B>(a: A, b: B) -> impl Stream<Item = Item> + Send
where
S: Stream<Item = Item> + Send + Unpin,
A: Stream<Item = Item> + Send,
B: Stream<Item = Item> + Send + Unpin,
Item: Eq + PartialOrd + Send + Sync,
{
use tokio::sync::Mutex;
+13 -8
View File
@@ -2,25 +2,30 @@
use crate::Result;
pub trait TryExpect<'a, Item> {
fn expect_ok(self) -> impl Stream<Item = Item> + Send + 'a;
pub trait TryExpect<Item>
where
Item: Send,
Self: Send + Sized,
{
fn expect_ok(self) -> impl Stream<Item = Item> + Send;
fn map_expect(self, msg: &'a str) -> impl Stream<Item = Item> + Send + 'a;
fn map_expect(self, msg: &str) -> impl Stream<Item = Item> + Send;
}
impl<'a, T, Item> TryExpect<'a, Item> for T
impl<Item, S> TryExpect<Item> for S
where
T: Stream<Item = Result<Item>> + Send + TryStream + 'a,
Item: 'a,
S: Stream<Item = Result<Item>> + Send + TryStream,
Item: Send,
Self: Send + Sized,
{
#[inline]
fn expect_ok(self: T) -> impl Stream<Item = Item> + Send + 'a {
fn expect_ok(self: S) -> impl Stream<Item = Item> + Send {
self.map_expect("stream expectation failure")
}
//TODO: move to impl MapExpect
#[inline]
fn map_expect(self, msg: &'a str) -> impl Stream<Item = Item> + Send + 'a {
fn map_expect(self, msg: &str) -> impl Stream<Item = Item> + Send {
self.map(|res| res.expect(msg))
}
}
+15 -14
View File
@@ -1,34 +1,35 @@
use futures::{Stream, StreamExt, TryStream, future::ready};
use crate::{Error, Result};
use crate::{Error, Result, utils::stream::TryExpect};
pub trait TryIgnore<'a, Item> {
fn ignore_err(self) -> impl Stream<Item = Item> + Send + 'a;
pub trait TryIgnore<Item>
where
Item: Send,
Self: Send + Sized,
{
fn ignore_err(self) -> impl Stream<Item = Item> + Send;
fn ignore_ok(self) -> impl Stream<Item = Error> + Send + 'a;
fn ignore_ok(self) -> impl Stream<Item = Error> + Send;
}
impl<'a, T, Item> TryIgnore<'a, Item> for T
impl<Item, S> TryIgnore<Item> for S
where
T: Stream<Item = Result<Item>> + TryStream + Send + 'a,
Item: Send + 'a,
S: Stream<Item = Result<Item>> + Send + TryStream + TryExpect<Item>,
Item: Send,
Self: Send + Sized,
{
#[cfg(debug_assertions)]
#[inline]
fn ignore_err(self: T) -> impl Stream<Item = Item> + Send + 'a {
use super::TryExpect;
self.expect_ok()
}
fn ignore_err(self: S) -> impl Stream<Item = Item> + Send { self.expect_ok() }
#[cfg(not(debug_assertions))]
#[inline]
fn ignore_err(self: T) -> impl Stream<Item = Item> + Send + 'a {
fn ignore_err(self: S) -> impl Stream<Item = Item> + Send {
self.filter_map(|res| ready(res.ok()))
}
#[inline]
fn ignore_ok(self: T) -> impl Stream<Item = Error> + Send + 'a {
fn ignore_ok(self: S) -> impl Stream<Item = Error> + Send {
self.filter_map(|res| ready(res.err()))
}
}
+18 -16
View File
@@ -13,20 +13,22 @@
mod try_wideband;
mod wideband;
pub use band::{
AMPLIFICATION_LIMIT, WIDTH_LIMIT, automatic_amplification, automatic_width,
set_amplification, set_width,
pub use self::{
band::{
AMPLIFICATION_LIMIT, WIDTH_LIMIT, automatic_amplification, automatic_width,
set_amplification, set_width,
},
broadband::BroadbandExt,
cloned::Cloned,
expect::TryExpect,
ignore::TryIgnore,
iter_stream::IterStream,
ready::ReadyExt,
tools::Tools,
try_broadband::TryBroadbandExt,
try_parallel::TryParallelExt,
try_ready::TryReadyExt,
try_tools::TryTools,
try_wideband::TryWidebandExt,
wideband::WidebandExt,
};
pub use broadband::BroadbandExt;
pub use cloned::Cloned;
pub use expect::TryExpect;
pub use ignore::TryIgnore;
pub use iter_stream::IterStream;
pub use ready::ReadyExt;
pub use tools::Tools;
pub use try_broadband::TryBroadbandExt;
pub use try_parallel::TryParallelExt;
pub use try_ready::TryReadyExt;
pub use try_tools::TryTools;
pub use try_wideband::TryWidebandExt;
pub use wideband::WidebandExt;
+4 -2
View File
@@ -4,8 +4,10 @@
mod unquote;
mod unquoted;
use std::mem::replace;
pub use self::{between::Between, split::SplitInfallible, unquote::Unquote, unquoted::Unquoted};
use crate::{Result, smallstr::SmallString, utils::exchange};
use crate::{Result, smallstr::SmallString};
pub const EMPTY: &str = "";
@@ -76,7 +78,7 @@ pub fn camel_to_snake_case<I, O>(output: &mut O, input: I) -> Result
.map(char::from)
.try_for_each(|ch| {
let m = ch.is_ascii_uppercase();
let s = exchange(&mut state, !m);
let s = replace(&mut state, !m);
if m && s {
output.write_char('_')?;
}
+2 -3
View File
@@ -37,11 +37,10 @@ pub(crate) fn db_options(config: &Config, env: &Env, row_cache: &Cache) -> Resul
opts.set_use_direct_io_for_flush_and_compaction(true);
}
if config.rocksdb_optimize_for_spinning_disks {
// speeds up opening DB on hard drives
opts.set_skip_checking_sst_file_sizes_on_db_open(true);
opts.set_skip_stats_update_on_db_open(true);
//opts.set_max_file_opening_threads(threads.try_into().unwrap());
} else {
opts.set_max_file_opening_threads(num_threads(config)?);
opts.set_compaction_readahead_size(1024 * 512);
}
@@ -123,7 +122,7 @@ fn set_logging_defaults(opts: &mut Options, config: &Config) {
if config.rocksdb_log_stderr {
opts.set_stderr_logger(rocksdb_log_level, "rocksdb");
} else {
opts.set_callback_logger(rocksdb_log_level, &handle_log);
opts.set_callback_logger(rocksdb_log_level, handle_log);
}
}
+13 -9
View File
@@ -1,6 +1,9 @@
use std::{convert::AsRef, fmt::Debug, sync::Arc};
use futures::{Future, FutureExt, TryFutureExt, future::ready};
use futures::{
Future, FutureExt, TryFutureExt,
future::{Either, ready},
};
use rocksdb::{DBPinnableSlice, ReadOptions};
use tokio::task;
use tuwunel_core::{Err, Result, err, implement, utils::result::MapExpect};
@@ -25,9 +28,9 @@ pub fn get<K>(
let cached = self.get_cached(key);
if matches!(cached, Err(_) | Ok(Some(_))) {
return task::consume_budget()
.map(move |()| cached.map_expect("data found in cache"))
.boxed();
return Either::Left(
task::consume_budget().map(move |()| cached.map_expect("data found in cache")),
);
}
debug_assert!(matches!(cached, Ok(None)), "expected status Incomplete");
@@ -37,11 +40,12 @@ pub fn get<K>(
res: None,
};
self.engine
.pool
.execute_get(cmd)
.and_then(|mut res| ready(res.remove(0)))
.boxed()
Either::Right(
self.engine
.pool
.execute_get(cmd)
.and_then(|mut res| ready(res.remove(0))),
)
}
/// Fetch a value from the cache without I/O.
+1 -1
View File
@@ -50,12 +50,12 @@ pub(crate) fn get_batch<'a, S, K>(
.widen_then(automatic_width(), |chunk| {
self.engine.pool.execute_get(Get {
map: self.clone(),
res: None,
key: chunk
.iter()
.map(AsRef::as_ref)
.map(Into::into)
.collect(),
res: None,
})
})
.map_ok(|results| results.into_iter().stream())
+1 -1
View File
@@ -51,7 +51,7 @@ pub fn insert_batch<'a, I, K, V>(&'a self, iter: I)
let write_options = &self.write_options;
self.engine
.db
.write_opt(batch, write_options)
.write_opt(&batch, write_options)
.or_else(or_else)
.expect("database insert batch error");
+15 -13
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::Deserialize;
use tokio::task;
@@ -27,11 +27,12 @@ pub fn raw_keys(self: &Arc<Self>) -> impl Stream<Item = Result<Key<'_>>> + Send
let state = stream::State::new(self, opts);
if is_cached(self) {
let state = state.init_fwd(None);
return task::consume_budget()
.map(move |()| stream::Keys::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::Keys::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -42,11 +43,12 @@ pub fn raw_keys(self: &Arc<Self>) -> impl Stream<Item = Result<Key<'_>>> + Send
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Keys<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Keys<'_>>()
.into_stream()
.try_flatten(),
)
}
+17 -9
View File
@@ -1,8 +1,9 @@
use std::{convert::AsRef, fmt::Debug, sync::Arc};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::{Deserialize, Serialize};
use tokio::task;
use tuwunel_core::{Result, implement};
use super::stream_from::is_cached;
@@ -64,7 +65,13 @@ pub fn raw_keys_from<P>(
let opts = super::iter_options_default(&self.engine);
let state = stream::State::new(self, opts);
if is_cached(self, from) {
return stream::Keys::<'_>::from(state.init_fwd(from.as_ref().into())).boxed();
let state = state.init_fwd(from.as_ref().into());
return Either::Left(
task::consume_budget()
.map(move |()| stream::Keys::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -75,11 +82,12 @@ pub fn raw_keys_from<P>(
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Keys<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Keys<'_>>()
.into_stream()
.try_flatten(),
)
}
+15 -13
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::Deserialize;
use tokio::task;
@@ -27,11 +27,12 @@ pub fn rev_raw_keys(self: &Arc<Self>) -> impl Stream<Item = Result<Key<'_>>> + S
let state = stream::State::new(self, opts);
if is_cached(self) {
let state = state.init_rev(None);
return task::consume_budget()
.map(move |()| stream::KeysRev::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::KeysRev::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -42,11 +43,12 @@ pub fn rev_raw_keys(self: &Arc<Self>) -> impl Stream<Item = Result<Key<'_>>> + S
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::KeysRev<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::KeysRev<'_>>()
.into_stream()
.try_flatten(),
)
}
+17 -9
View File
@@ -1,8 +1,9 @@
use std::{convert::AsRef, fmt::Debug, sync::Arc};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::{Deserialize, Serialize};
use tokio::task;
use tuwunel_core::{Result, implement};
use super::rev_stream_from::is_cached;
@@ -64,7 +65,13 @@ pub fn rev_raw_keys_from<P>(
let opts = super::iter_options_default(&self.engine);
let state = stream::State::new(self, opts);
if is_cached(self, from) {
return stream::KeysRev::<'_>::from(state.init_rev(from.as_ref().into())).boxed();
let state = state.init_rev(from.as_ref().into());
return Either::Left(
task::consume_budget()
.map(move |()| stream::KeysRev::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -75,11 +82,12 @@ pub fn rev_raw_keys_from<P>(
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::KeysRev<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::KeysRev<'_>>()
.into_stream()
.try_flatten(),
)
}
+15 -13
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::Deserialize;
use tokio::task;
@@ -35,11 +35,12 @@ pub fn rev_raw_stream(self: &Arc<Self>) -> impl Stream<Item = Result<KeyVal<'_>>
let state = stream::State::new(self, opts);
if is_cached(self) {
let state = state.init_rev(None);
return task::consume_budget()
.map(move |()| stream::ItemsRev::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::ItemsRev::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -50,13 +51,14 @@ pub fn rev_raw_stream(self: &Arc<Self>) -> impl Stream<Item = Result<KeyVal<'_>>
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::ItemsRev<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::ItemsRev<'_>>()
.into_stream()
.try_flatten(),
)
}
#[tracing::instrument(
+15 -13
View File
@@ -1,6 +1,6 @@
use std::{convert::AsRef, fmt::Debug, sync::Arc};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::{Deserialize, Serialize};
use tokio::task;
@@ -84,11 +84,12 @@ pub fn rev_raw_stream_from<P>(
let state = stream::State::new(self, opts);
if is_cached(self, from) {
let state = state.init_rev(from.as_ref().into());
return task::consume_budget()
.map(move |()| stream::ItemsRev::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::ItemsRev::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -99,13 +100,14 @@ pub fn rev_raw_stream_from<P>(
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::ItemsRev<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::ItemsRev<'_>>()
.into_stream()
.try_flatten(),
)
}
#[tracing::instrument(
+15 -13
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::Deserialize;
use tokio::task;
@@ -35,11 +35,12 @@ pub fn raw_stream(self: &Arc<Self>) -> impl Stream<Item = Result<KeyVal<'_>>> +
let state = stream::State::new(self, opts);
if is_cached(self) {
let state = state.init_fwd(None);
return task::consume_budget()
.map(move |()| stream::Items::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::Items::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -50,13 +51,14 @@ pub fn raw_stream(self: &Arc<Self>) -> impl Stream<Item = Result<KeyVal<'_>>> +
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Items<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Items<'_>>()
.into_stream()
.try_flatten(),
)
}
#[tracing::instrument(
+15 -13
View File
@@ -1,6 +1,6 @@
use std::{convert::AsRef, fmt::Debug, sync::Arc};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::Either};
use rocksdb::Direction;
use serde::{Deserialize, Serialize};
use tokio::task;
@@ -83,11 +83,12 @@ pub fn raw_stream_from<P>(
let state = stream::State::new(self, opts);
if is_cached(self, from) {
let state = state.init_fwd(from.as_ref().into());
return task::consume_budget()
.map(move |()| stream::Items::<'_>::from(state))
.into_stream()
.flatten()
.boxed();
return Either::Left(
task::consume_budget()
.map(move |()| stream::Items::<'_>::from(state))
.into_stream()
.flatten(),
);
}
let seek = Seek {
@@ -98,13 +99,14 @@ pub fn raw_stream_from<P>(
res: None,
};
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Items<'_>>()
.into_stream()
.try_flatten()
.boxed()
Either::Right(
self.engine
.pool
.execute_iter(seek)
.ok_into::<stream::Items<'_>>()
.into_stream()
.try_flatten(),
)
}
#[tracing::instrument(
-16
View File
@@ -287,22 +287,6 @@ fn worker_init(&self, id: usize) {
// affinity is empty (no-op) if there's only one queue
set_affinity(affinity.clone());
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
if affinity.clone().count() == 1 && tuwunel_core::alloc::je::is_affine_arena() {
use tuwunel_core::{
alloc::je::this_thread::{arena_id, set_arena},
result::LogDebugErr,
};
let id = affinity.clone().next().expect("at least one id");
if let Ok(arena) = arena_id() {
if arena != id {
set_arena(id).log_debug_err().ok();
}
}
}
trace!(
?group,
affinity = ?affinity.collect::<Vec<_>>(),
+3 -3
View File
@@ -1,7 +1,7 @@
use std::io::Write;
use std::{io::Write, mem::replace};
use serde::{Deserialize, Serialize, ser};
use tuwunel_core::{Error, Result, debug::type_name, err, result::DebugInspect, utils::exchange};
use tuwunel_core::{Error, Result, debug::type_name, err, result::DebugInspect};
use crate::util::unhandled;
@@ -97,7 +97,7 @@ fn sequence_end(&mut self) -> Result {
fn record_start(&mut self) -> Result {
debug_assert!(!self.is_finalized(), "Starting a record after serialization finalized");
exchange(&mut self.sep, true)
replace(&mut self.sep, true)
.then(|| self.separator())
.unwrap_or(Ok(()))
}
+7 -7
View File
@@ -3,10 +3,10 @@
mod keys;
mod keys_rev;
use std::sync::Arc;
use std::{mem::replace, sync::Arc};
use rocksdb::{DBRawIteratorWithThreadMode, ReadOptions};
use tuwunel_core::{Result, utils::exchange};
use tuwunel_core::Result;
pub(crate) use self::{items::Items, items_rev::ItemsRev, keys::Keys, keys_rev::KeysRev};
use crate::{
@@ -22,7 +22,7 @@ pub(crate) struct State<'a> {
init: bool,
}
pub(crate) trait Cursor<'a, T> {
pub(crate) trait Cursor<'a, T>: Send {
fn state(&self) -> &State<'a>;
fn fetch(&self) -> Option<T>;
@@ -50,12 +50,12 @@ impl<'a> State<'a> {
#[inline]
pub(super) fn new(map: &'a Arc<Map>, opts: ReadOptions) -> Self {
Self {
init: true,
seek: false,
inner: map
.engine()
.db
.raw_iterator_cf_opt(&map.cf(), opts),
init: true,
seek: false,
}
}
@@ -94,7 +94,7 @@ pub(super) fn init_rev(mut self, from: From<'_>) -> Self {
#[inline]
#[cfg_attr(unabridged, tracing::instrument(level = "trace", skip_all))]
pub(super) fn seek_fwd(&mut self) {
if !exchange(&mut self.init, false) {
if !replace(&mut self.init, false) {
self.inner.next();
} else if !self.seek {
self.inner.seek_to_first();
@@ -104,7 +104,7 @@ pub(super) fn seek_fwd(&mut self) {
#[inline]
#[cfg_attr(unabridged, tracing::instrument(level = "trace", skip_all))]
pub(super) fn seek_rev(&mut self) {
if !exchange(&mut self.init, false) {
if !replace(&mut self.init, false) {
self.inner.prev();
} else if !self.seek {
self.inner.seek_to_last();
+32 -17
View File
@@ -1,11 +1,12 @@
use std::sync::Arc;
use tracing::subscriber::NoSubscriber;
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, reload};
use tuwunel_core::{
Result,
config::Config,
debug_warn, err,
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, Logging, capture, fmt_span},
result::UnwrapOrErr,
};
@@ -14,13 +15,20 @@
Option<tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>>;
#[cfg(not(feature = "perf_measurements"))]
pub(crate) type TracingFlameGuard = ();
pub(crate) type TracingFlameGuard = Option<()>;
#[allow(clippy::redundant_clone)]
pub(crate) fn init(
config: &Config,
) -> Result<(LogLevelReloadHandles, TracingFlameGuard, Arc<capture::State>)> {
pub(crate) fn init(config: &Config) -> Result<(TracingFlameGuard, Logging)> {
let reload_handles = LogLevelReloadHandles::default();
let cap_state = Arc::new(capture::State::new());
if !config.log_enable {
return Ok((None, Logging {
reload: reload_handles,
capture: cap_state,
subscriber: Arc::new(NoSubscriber::new()),
}));
}
let console_span_events = fmt_span::from_str(&config.log_span_events).unwrap_or_err();
@@ -37,14 +45,11 @@ pub(crate) fn init(
.event_format(ConsoleFormat::new(config))
.with_writer(ConsoleWriter::new(config));
let (console_reload_filter, console_reload_handle) =
reload::Layer::new(console_filter.clone());
let (console_reload_filter, console_reload_handle) = reload::Layer::new(console_filter);
reload_handles.add("console", Box::new(console_reload_handle));
let cap_state = Arc::new(capture::State::new());
let cap_layer = capture::Layer::new(&cap_state);
let subscriber = Registry::default()
.with(console_layer.with_filter(console_reload_filter))
.with(cap_layer);
@@ -117,33 +122,43 @@ pub(crate) fn init(
not(feature = "perf_measurements"),
allow(clippy::let_unit_value)
)]
let flame_guard = ();
let flame_guard = None;
let ret = (reload_handles, flame_guard, cap_state);
let subscriber = Arc::new(subscriber);
// Enable the tokio console. This is slightly kludgy because we're judggling
// compile-time and runtime conditions to elide it, each of those changing the
// subscriber's type.
let (console_enabled, console_disabled_reason) = tokio_console_enabled(config);
#[cfg(all(feature = "tokio_console", tokio_unstable, tuwunel_disable))]
if console_enabled {
if console_enabled && config.log_global_default {
let console_layer = console_subscriber::ConsoleLayer::builder()
.with_default_env()
.spawn();
set_global_default(subscriber.with(console_layer));
return Ok(ret);
set_global_default(subscriber.clone().with(console_layer));
return Ok((flame_guard, Log {
reload: reload_handles,
capture: cap_state,
subscriber,
}));
}
set_global_default(subscriber);
if config.log_global_default {
set_global_default(subscriber.clone());
}
// If there's a reason the tokio console was disabled when it might be desired
// we output that here after initializing logging
if !console_enabled && !console_disabled_reason.is_empty() {
if !console_enabled && !console_disabled_reason.is_empty() && config.log_global_default {
debug_warn!("{console_disabled_reason}");
}
Ok(ret)
Ok((flame_guard, Logging {
reload: reload_handles,
capture: cap_state,
subscriber,
}))
}
fn tokio_console_enabled(config: &Config) -> (bool, &'static str) {
+2 -9
View File
@@ -174,15 +174,8 @@ fn set_worker_affinity() {
}
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
fn set_worker_mallctl(id: usize) {
use tuwunel_core::alloc::je::{
is_affine_arena,
this_thread::{set_arena, set_muzzy_decay},
};
if is_affine_arena() {
set_arena(id).log_debug_err().ok();
}
fn set_worker_mallctl(_id: usize) {
use tuwunel_core::alloc::je::this_thread::set_muzzy_decay;
let muzzy_option = GC_MUZZY
.get()
+1 -4
View File
@@ -5,7 +5,6 @@
Error, Result,
config::Config,
implement, info,
log::Log,
utils::{stream, sys},
};
@@ -45,7 +44,7 @@ pub fn new(args: Option<&Args>, runtime: Option<&runtime::Handle>) -> Result<Arc
.and_then(|raw| args::update(raw, args))
.and_then(|raw| Config::new(&raw))?;
let (tracing_reload_handle, tracing_flame_guard, capture) = crate::logging::init(&config)?;
let (tracing_flame_guard, logger) = crate::logging::init(&config)?;
config.check()?;
@@ -67,8 +66,6 @@ pub fn new(args: Option<&Args>, runtime: Option<&runtime::Handle>) -> Result<Arc
tuwunel_core::version(),
);
let logger = Log { reload: tracing_reload_handle, capture };
Ok(Arc::new(Self {
server: Arc::new(tuwunel_core::Server::new(config, runtime.cloned(), logger)),
+30 -11
View File
@@ -8,11 +8,12 @@
use http::{
HeaderValue, Method, StatusCode,
header::{self, HeaderName},
uri::PathAndQuery,
};
use tower::ServiceBuilder;
use tower_http::{
catch_panic::CatchPanicLayer,
cors::{self, CorsLayer},
cors::{AllowOrigin, CorsLayer},
sensitive_headers::SetSensitiveHeadersLayer,
set_header::SetResponseHeaderLayer,
timeout::{RequestBodyTimeoutLayer, ResponseBodyTimeoutLayer, TimeoutLayer},
@@ -138,30 +139,48 @@ fn compression_layer(server: &Server) -> tower_http::compression::CompressionLay
compression_layer
}
fn cors_layer(_server: &Server) -> CorsLayer {
fn cors_layer(server: &Server) -> CorsLayer {
const METHODS: [Method; 7] = [
Method::DELETE,
Method::GET,
Method::HEAD,
Method::OPTIONS,
Method::PATCH,
Method::POST,
Method::PUT,
Method::DELETE,
Method::OPTIONS,
];
let headers: [HeaderName; 5] = [
header::ORIGIN,
HeaderName::from_lowercase(b"x-requested-with").unwrap(),
header::CONTENT_TYPE,
header::ACCEPT,
header::AUTHORIZATION,
header::CONTENT_TYPE,
header::ORIGIN,
HeaderName::from_lowercase(b"x-requested-with").unwrap(),
];
let allow_origin_list = server
.config
.access_control_allow_origin
.iter()
.map(AsRef::as_ref)
.map(HeaderValue::from_str)
.filter_map(Result::ok);
let allow_origin = if !server
.config
.access_control_allow_origin
.is_empty()
{
AllowOrigin::list(allow_origin_list)
} else {
AllowOrigin::any()
};
CorsLayer::new()
.allow_origin(cors::Any)
.max_age(Duration::from_secs(86400))
.allow_methods(METHODS)
.allow_headers(headers)
.max_age(Duration::from_secs(86400))
.allow_origin(allow_origin)
}
fn body_limit_layer(server: &Server) -> DefaultBodyLimit {
@@ -221,8 +240,8 @@ fn request_path_str<T>(request: &http::Request<T>) -> &str {
request
.uri()
.path_and_query()
.expect("all requests have a path")
.as_str()
.map(PathAndQuery::as_str)
.unwrap_or("/")
}
fn truncated_matched_path(path: &MatchedPath) -> &str {
+2 -1
View File
@@ -19,7 +19,7 @@
name = "request",
level = "debug",
skip_all,
err(Debug)
err(Debug, level = "debug")
fields(
id = %services
.server
@@ -71,6 +71,7 @@ pub(crate) async fn handle(
level = "debug",
parent = parent,
skip_all,
ret(level = "trace"),
)]
#[allow(unused_variables)]
async fn execute(
+7 -4
View File
@@ -3,7 +3,7 @@
use std::sync::Arc;
use futures::{Stream, StreamExt, TryFutureExt};
use futures::{Stream, StreamExt, TryFutureExt, pin_mut};
use ruma::{
RoomId, UserId,
events::{
@@ -175,15 +175,18 @@ pub async fn last_count<'a>(
let upper = upper.unwrap_or(u64::MAX);
let key = (room_id, user_id, upper, Interfix);
self.db
let keys = self
.db
.roomuserdataid_accountdata
.rev_keys_from(&key)
.ignore_err()
.ready_take_while(move |(room_id_, user_id_, ..): &Key<'_>| {
room_id == *room_id_ && user_id == *user_id_
})
.map(at!(2))
.next()
.map(at!(2));
pin_mut!(keys);
keys.next()
.await
.ok_or_else(|| err!(Request(NotFound("No account data found."))))
}
+1 -1
View File
@@ -145,7 +145,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
if self.services.server.config.admin_room_notices {
let welcome_message = String::from(
"## Thank you for trying out tuwunel!\n\nTuwunel is a continuation of conduwuit which was technically a hard fork of Conduit.\n\nHelpful links:\n> GitHub Repo: https://github.com/matrix-construct/tuwunel\n> Documentation: https://github.com/matrix-construct/tuwunel\n> Report issues: https://github.com/matri-construct/tuwunel/issues\n\nFor a list of available commands, send the following message in this room: `!admin --help`"
"## Thank you for trying out tuwunel!\n\nTuwunel is a continuation of conduwuit which was technically a hard fork of Conduit.\n\nHelpful links:\n> GitHub Repo: https://github.com/matrix-construct/tuwunel\n> Documentation: https://github.com/matrix-construct/tuwunel\n> Report issues: https://github.com/matrix-construct/tuwunel/issues\n\nFor a list of available commands, send the following message in this room: `!admin --help`"
);
// Send welcome message
-1
View File
@@ -16,7 +16,6 @@
use tuwunel_core::{Err, Result, debug, err, utils::stream::IterStream};
use tuwunel_database::Map;
pub(crate) use self::request::send_request;
pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
pub struct Service {
+5 -4
View File
@@ -1,19 +1,19 @@
use std::{fmt::Debug, mem};
use bytes::BytesMut;
use reqwest::Client;
use ruma::api::{
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, SupportedVersions,
appservice::Registration,
};
use tuwunel_core::{Err, Result, debug_error, err, trace, utils, warn};
use tuwunel_core::{Err, Result, debug_error, err, implement, trace, utils, warn};
/// Sends a request to an appservice
///
/// Only returns Ok(None) if there is no url specified in the appservice
/// registration file
pub(crate) async fn send_request<T>(
client: &Client,
#[implement(super::Service)]
pub async fn send_request<T>(
&self,
registration: Registration,
request: T,
) -> Result<Option<T::IncomingResponse>>
@@ -25,6 +25,7 @@ pub(crate) async fn send_request<T>(
versions: VERSIONS.into(),
features: Default::default(),
};
let client = &self.services.client.appservice;
let Some(dest) = registration.url else {
return Ok(None);
+2 -2
View File
@@ -163,7 +163,7 @@ async fn into_http_response(
request_url = ?url,
response_url = ?response.url(),
"Received response from {}",
actual.string(),
actual.to_string(),
);
let mut http_response_builder = http::Response::builder()
@@ -249,7 +249,7 @@ fn to_http_request<T>(
};
let mut request = request
.try_into_http_request::<Vec<u8>>(actual.string().as_str(), SATIR, &supported)
.try_into_http_request::<Vec<u8>>(actual.to_string().as_str(), SATIR, &supported)
.map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))?;
if matches!(T::METADATA.authentication, AuthScheme::ServerSignatures) {
+6 -3
View File
@@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration};
use futures::StreamExt;
use futures::{StreamExt, pin_mut};
use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition};
use tuwunel_core::{
Err, Result, debug, debug_info, err,
@@ -109,11 +109,14 @@ pub(super) async fn search_file_metadata(
let dim: &[u32] = &[dim.width, dim.height];
let prefix = (mxc, dim, Interfix);
let key = self
let keys = self
.mediaid_file
.keys_prefix_raw(&prefix)
.ignore_err()
.map(ToOwned::to_owned)
.map(ToOwned::to_owned);
pin_mut!(keys);
let key = keys
.next()
.await
.ok_or_else(|| err!(Request(NotFound("Media not found"))))?;
+6 -6
View File
@@ -320,8 +320,8 @@ async fn federation_request<Request>(
Request: OutgoingRequest + Send + Debug,
{
self.services
.sending
.send_federation_request(server.unwrap_or(mxc.server_name), request)
.federation
.execute(server.unwrap_or(mxc.server_name), request)
.await
.map_err(|error| handle_federation_error(mxc, user, server, error))
}
@@ -374,8 +374,8 @@ pub async fn fetch_remote_thumbnail_legacy(
self.check_fetch_authorized(&mxc)?;
let response = self
.services
.sending
.send_federation_request(mxc.server_name, media::get_content_thumbnail::v3::Request {
.federation
.execute(mxc.server_name, media::get_content_thumbnail::v3::Request {
allow_remote: body.allow_remote,
height: body.height,
width: body.width,
@@ -414,8 +414,8 @@ pub async fn fetch_remote_content_legacy(
self.check_fetch_authorized(mxc)?;
let response = self
.services
.sending
.send_federation_request(mxc.server_name, media::get_content::v3::Request {
.federation
.execute(mxc.server_name, media::get_content::v3::Request {
allow_remote: true,
server_name: mxc.server_name.into(),
media_id: mxc.media_id.into(),
+4 -3
View File
@@ -5,7 +5,7 @@
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{
Err, Result, err, implement, matrix::event::gen_event_id_canonical_json, pdu::PduBuilder,
Err, Result, at, err, implement, matrix::event::gen_event_id_canonical_json, pdu::PduBuilder,
};
use super::Service;
@@ -83,8 +83,8 @@ async fn remote_invite(
let response = self
.services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
.federation
.execute(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
@@ -135,6 +135,7 @@ async fn remote_invite(
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.await?
.map(at!(0))
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
})?;
+31 -32
View File
@@ -251,8 +251,8 @@ pub async fn join_remote(
let send_join_response = match self
.services
.sending
.send_synapse_request(&remote_server, send_join_request)
.federation
.execute(&remote_server, send_join_request)
.await
{
| Ok(response) => response,
@@ -704,20 +704,17 @@ pub async fn join_local(
let send_join_response = self
.services
.sending
.send_synapse_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: self
.services
.federation
.format_pdu_into(join_event.clone(), Some(&room_version_id))
.await,
},
)
.federation
.execute(&remote_server, federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: self
.services
.federation
.format_pdu_into(join_event.clone(), Some(&room_version_id))
.await,
})
.await?;
if let Some(signed_raw) = send_join_response.room_state.event {
@@ -774,19 +771,16 @@ async fn make_join_request(
info!("Asking {remote_server} for make_join ({make_join_counter})");
let make_join_response = self
.services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_join_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: self
.services
.server
.supported_room_versions()
.collect(),
},
)
.federation
.execute(remote_server, federation::membership::prepare_join_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: self
.services
.server
.supported_room_versions()
.collect(),
})
.await;
trace!("make_join response: {make_join_response:?}");
@@ -812,10 +806,15 @@ async fn make_join_request(
return make_join_response_and_server;
}
if make_join_counter > 40 {
let max_attempts = self
.services
.config
.max_make_join_attempts_per_join_attempt;
if make_join_counter >= max_attempts {
warn!(?remote_server, "last make_join failure reason: {e}");
warn!(
"40 servers failed to provide valid make_join response, assuming no server \
can assist in joining."
"{max_attempts} servers failed to provide valid make_join response, \
assuming no server can assist in joining."
);
make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
+31 -35
View File
@@ -1,6 +1,6 @@
use std::collections::HashSet;
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use futures::{FutureExt, StreamExt, TryFutureExt, future::ready, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
api::federation,
@@ -13,7 +13,7 @@
Err, Result, debug_info, debug_warn, err, implement,
matrix::PduCount,
pdu::PduBuilder,
utils::{self, FutureBoolExt, future::ReadyEqExt},
utils::{self, FutureBoolExt, future::ReadyBoolExt},
warn,
};
@@ -70,17 +70,28 @@ pub async fn leave(
return Ok(());
}
let member_event = self
.services
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(
room_id,
&StateEventType::RoomMember,
user_id.as_str(),
)
.await;
let dont_have_room = self
.services
.state_cache
.server_in_room(self.services.globals.server_name(), room_id)
.eq(&false);
.is_false()
.and(ready(member_event.as_ref().is_err()));
let not_knocked = self
.services
.state_cache
.is_knocked(user_id, room_id)
.eq(&false);
.is_false();
// Ask a remote server if we don't have this room and are not knocking on it
if remote_leave_now || dont_have_room.and(not_knocked).await {
@@ -122,16 +133,7 @@ pub async fn leave(
)
.await?;
} else {
let Ok(event) = self
.services
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(
room_id,
&StateEventType::RoomMember,
user_id.as_str(),
)
.await
else {
let Ok(event) = member_event else {
debug_warn!(
"Trying to leave a room you are not a member of, marking room as left locally."
);
@@ -243,14 +245,11 @@ async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
{
let make_leave_response = self
.services
.sending
.send_federation_request(
&remote_server,
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
)
.federation
.execute(&remote_server, federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
})
.await;
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
@@ -317,19 +316,16 @@ async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
let leave_event = leave_event_stub;
self.services
.sending
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: self
.services
.federation
.format_pdu_into(leave_event.clone(), Some(&room_version_id))
.await,
},
)
.federation
.execute(&remote_server, federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: self
.services
.federation
.format_pdu_into(leave_event.clone(), Some(&room_version_id))
.await,
})
.await?;
Ok(())
+27 -31
View File
@@ -10,19 +10,20 @@
use tuwunel_core::{Err, Result, debug, debug_info, err, error, trace};
use super::{
DestString, FedDest,
cache::{CachedDest, CachedOverride, MAX_IPS},
fed::{FedDest, PortString, add_port_to_hostname, get_ip_with_port},
fed::{PortString, add_port_to_hostname, get_ip_with_port},
};
#[derive(Clone, Debug)]
pub(crate) struct ActualDest {
pub(crate) dest: FedDest,
pub(crate) host: String,
pub(crate) host: DestString,
}
impl ActualDest {
#[inline]
pub(crate) fn string(&self) -> String { self.dest.https_string() }
pub(crate) fn to_string(&self) -> DestString { self.dest.https_string() }
}
impl super::Service {
@@ -65,7 +66,7 @@ pub async fn resolve_actual_dest(
cache: bool,
) -> Result<CachedDest> {
self.validate_dest(dest)?;
let mut host = dest.as_str().to_owned();
let mut host: DestString = dest.as_str().into();
let actual_dest = match get_ip_with_port(dest.as_str()) {
| Some(host_port) => Self::actual_dest_1(host_port)?,
| None =>
@@ -77,7 +78,7 @@ pub async fn resolve_actual_dest(
self.services.server.check_running()?;
match self.request_well_known(dest.as_str()).await? {
| Some(delegated) =>
self.actual_dest_3(&mut host, cache, delegated)
self.actual_dest_3(&mut host, cache, &delegated)
.await?,
| _ => match self.query_srv_record(dest.as_str()).await? {
| Some(overrider) =>
@@ -94,16 +95,16 @@ pub async fn resolve_actual_dest(
let host = if let Ok(addr) = host.parse::<SocketAddr>() {
FedDest::Literal(addr)
} else if let Ok(addr) = host.parse::<IpAddr>() {
FedDest::Named(addr.to_string(), FedDest::default_port())
FedDest::Named(addr.to_string().into(), FedDest::default_port())
} else if let Some(pos) = host.find(':') {
let (host, port) = host.split_at(pos);
FedDest::Named(
host.to_owned(),
host.into(),
port.try_into()
.unwrap_or_else(|_| FedDest::default_port()),
)
} else {
FedDest::Named(host, FedDest::default_port())
FedDest::Named(host.as_str().into(), FedDest::default_port())
};
debug!("Actual destination: {actual_dest:?} hostname: {host:?}");
@@ -126,7 +127,7 @@ async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Res
.await?;
Ok(FedDest::Named(
host.to_owned(),
host.into(),
port.try_into()
.unwrap_or_else(|_| FedDest::default_port()),
))
@@ -134,20 +135,20 @@ async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Res
async fn actual_dest_3(
&self,
host: &mut String,
host: &mut DestString,
cache: bool,
delegated: String,
delegated: &str,
) -> Result<FedDest> {
debug!("3: A .well-known file is available");
*host = add_port_to_hostname(&delegated).uri_string();
match get_ip_with_port(&delegated) {
*host = add_port_to_hostname(delegated).uri_string();
match get_ip_with_port(delegated) {
| Some(host_and_port) => Self::actual_dest_3_1(host_and_port),
| None =>
if let Some(pos) = delegated.find(':') {
self.actual_dest_3_2(cache, delegated, pos).await
} else {
trace!("Delegated hostname has no port in this branch");
match self.query_srv_record(&delegated).await? {
match self.query_srv_record(delegated).await? {
| Some(overrider) =>
self.actual_dest_3_3(cache, delegated, overrider)
.await,
@@ -162,19 +163,14 @@ fn actual_dest_3_1(host_and_port: FedDest) -> Result<FedDest> {
Ok(host_and_port)
}
async fn actual_dest_3_2(
&self,
cache: bool,
delegated: String,
pos: usize,
) -> Result<FedDest> {
async fn actual_dest_3_2(&self, cache: bool, delegated: &str, pos: usize) -> Result<FedDest> {
debug!("3.2: Hostname with port in .well-known file");
let (host, port) = delegated.split_at(pos);
self.conditional_query_and_cache(host, port.parse::<u16>().unwrap_or(8448), cache)
.await?;
Ok(FedDest::Named(
host.to_owned(),
host.into(),
port.try_into()
.unwrap_or_else(|_| FedDest::default_port()),
))
@@ -183,13 +179,13 @@ async fn actual_dest_3_2(
async fn actual_dest_3_3(
&self,
cache: bool,
delegated: String,
delegated: &str,
overrider: FedDest,
) -> Result<FedDest> {
debug!("3.3: SRV lookup successful");
let force_port = overrider.port();
self.conditional_query_and_cache_override(
&delegated,
delegated,
&overrider.hostname(),
force_port.unwrap_or(8448),
cache,
@@ -198,7 +194,7 @@ async fn actual_dest_3_3(
if let Some(port) = force_port {
return Ok(FedDest::Named(
delegated,
delegated.into(),
format!(":{port}")
.as_str()
.try_into()
@@ -206,14 +202,15 @@ async fn actual_dest_3_3(
));
}
Ok(add_port_to_hostname(&delegated))
Ok(add_port_to_hostname(delegated))
}
async fn actual_dest_3_4(&self, cache: bool, delegated: String) -> Result<FedDest> {
async fn actual_dest_3_4(&self, cache: bool, delegated: &str) -> Result<FedDest> {
debug!("3.4: No SRV records, just use the hostname from .well-known");
self.conditional_query_and_cache(&delegated, 8448, cache)
self.conditional_query_and_cache(delegated, 8448, cache)
.await?;
Ok(add_port_to_hostname(&delegated))
Ok(add_port_to_hostname(delegated))
}
async fn actual_dest_4(
@@ -234,9 +231,8 @@ async fn actual_dest_4(
if let Some(port) = force_port {
let port = format!(":{port}");
return Ok(FedDest::Named(
host.to_owned(),
host.into(),
PortString::from(port.as_str()).unwrap_or_else(|_| FedDest::default_port()),
));
}
@@ -334,7 +330,7 @@ async fn query_srv_record(&self, hostname: &'_ str) -> Result<Option<FedDest>> {
.target()
.to_string()
.trim_end_matches('.')
.to_owned(),
.into(),
format!(":{}", result.port())
.as_str()
.try_into()
+3 -3
View File
@@ -11,7 +11,7 @@
};
use tuwunel_database::{Cbor, Deserialized, Map};
use super::fed::FedDest;
use super::{DestString, FedDest};
pub struct Cache {
destinations: Arc<Map>,
@@ -21,7 +21,7 @@ pub struct Cache {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CachedDest {
pub dest: FedDest,
pub host: String,
pub host: DestString,
pub expire: SystemTime,
}
@@ -30,7 +30,7 @@ pub struct CachedOverride {
pub ips: IpAddrs,
pub port: u16,
pub expire: SystemTime,
pub overriding: Option<String>,
pub overriding: Option<DestString>,
}
pub type IpAddrs = ArrayVec<IpAddr, MAX_IPS>;

Some files were not shown because too many files have changed in this diff Show More