Compare commits

...
145 Commits
Author SHA1 Message Date
Jason Volk 9af15ecbba Bump 1.4.5
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 13:33:32 +00:00
Jason Volk f66a83763e Persist sliding-sync state; mitigate initial-sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 13:33:32 +00:00
Jason Volk 718c3adcb2 ci: Expand the rust-sdk integration matrix.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:44:16 +00:00
Jason Volk af80482c04 Add branch in room handler for left rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:34:49 +00:00
Jason Volk a20ddcd586 Recency bump on own membership event.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:08:26 +00:00
Jason Volk b483306367 Fix sliding-sync invite regression.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 07:52:40 +00:00
Jason Volk f5e98467be Improve sliding-sync robustness to deeper replays.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-24 18:31:48 +00:00
Jason Volk d44db45f83 Adjust the order of operations during read_marker handling.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-24 18:31:48 +00:00
Jason Volk 0397bb8237 Implement specified non-blocking semantic for sliding-sync.
Simplify sliding-sync watch loop and bounds.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 11:00:55 +00:00
Jason Volk 4010fc62bc Use take_while rather than filter for scanning last_receipt_count.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 03:37:52 +00:00
Jason Volk 0d823a2822 Revert "Involve m.fully_read for resetting notification counts."
This reverts commit 0df5e5e7ac.
2025-10-23 03:37:52 +00:00
Jason Volk 9cd175b125 Additional logging/tracing tweaks around sliding-sync.
Additional spans around receipt service interface.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 03:37:52 +00:00
Jason Volk 050a1a350a Add dbg profile to Cargo; default to faster builds accomodating new users. 2025-10-23 03:37:52 +00:00
Jason Volk ec0f872f8f Add tracing around db watcher notify including cf name.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 20:23:30 +00:00
Jason Volk 024e8eae62 Implement num_live for sliding-sync room response.
Avoid using origin_server_ts for recency stamp.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 09:14:41 +00:00
Jason Volk 4fd60b2605 Improve sliding-sync selector and windowing logic.
Bump Ruma; eliminate premature release compat.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 08:15:25 +00:00
Jason Volk e53968d9eb Improve tracing spans for notif counts and sliding-sync exts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 07:05:03 +00:00
Jason Volk 7cbc2ee385 Fix the accidentally dual-keyspaced cf from conduit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 07:03:10 +00:00
Jason Volk 0df5e5e7ac Involve m.fully_read for resetting notification counts.
EXA is not reliably removing its unread marker any other way. Clients
wishing to move the fully_read marker in the other direction should
file an issue to make themselves known.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 06:52:30 +00:00
Jason Volk 312eb69450 Add tool to get sibling connections for a device.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 17:48:08 +00:00
Jason Volk 5b620a2c37 Add .and_is() / .and_if() to the BoolExt smalltalk.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 17:41:42 +00:00
Jason Volk 3b4fbb8c1a Fix inverted debug assert message.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 15:48:05 +00:00
Jason Volk 158d44e1a9 docs: Remove line about other reverse proxies. (#197)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 15:02:36 +00:00
Jason Volk d2aab468cf Fixes for sliding-sync request replays.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 06:46:02 +00:00
Jason Volk aa4486dfdf Simplify types, mitigate expansion; eliminate unnecessary move.
Further simplify future::BoolExt toward type expansion mitigation.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 05:25:11 +00:00
Jason Volk 2a662445b6 Make account_data.last_count() upper bound argument optional.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:58:38 +00:00
Jason Volk 5b3f0fde23 Add invite rooms to sync v3 watch rooms list.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:57:52 +00:00
Jason Volk 9640afebff Join the async prologue operations in sync v3.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:55:42 +00:00
Jason Volk dd5c5c7a4a Notify watchers on delete operations in addition to inserts. 2025-10-20 20:45:38 +00:00
Jason Volk fc0f04defa Optimize watcher range collection on notify.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 20:44:45 +00:00
Jason Volk 7d8f7cbe5d Bump 1.4.4
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-16 01:55:43 +00:00
Jason Volk 4e0249cd2f Fix sliding-sync again by resetting after interrupted requests. (fixes #190)
This solution is not elegant but appears to be correct for fixing the issue. A
a more efficient solution might be possible without resetting the connection
which should be further investigated.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-16 01:05:16 +00:00
Jason Volk d5b39aa995 Bump 1.4.3
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-11 02:12:43 +00:00
Jason Volk 46c940b863 Refactor sliding window selector. (fixes #170)
Refactor list filtering.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 15:56:08 +00:00
Jason Volk ab8536d5c3 Add query to get latest account data count.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 12:44:23 +00:00
Jason Volk 4918868632 Add query to get last read receipt for room.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 12:44:23 +00:00
Jason Volk 9e00f70197 Allow nursery clippy::tuple_array_conversions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 08:30:08 +00:00
Jason Volk d3aaf9e4a9 Additional FutureBoolExt; attempt to reduce type-length expansion.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 96dc56ad07 Relax clippy::rest-pat-in-fully-bound-structs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk e12b0262da Fix unnecessary async for future and/or utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk e5bf005eaf Simplify state_cache.user_memberships() masking for callers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 02ccb1dceb Cleanup legacy config path component.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk a450eb96eb ci: Remove default-features builds from publication to registries.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 155af0fda3 Add iteration of space children; simplify space child event fetcher.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 5c61b4d4a3 Add room state accessor iterating state_keys joined by respective event_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 97e709492c Add is_direct() alternative query for m.room.member.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 51fc2342a4 Add is_direct() query for account_data.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 4ca68deef8 Add better interface for getting and setting room tag account data.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 4d8d64f5c7 Add missing wildcard matching for sliding-sync required state.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk dff30e5924 Additional state accessor iterations for pdus of a specific type.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 7fee459b1a Add admin diagnostic query suite for sync state.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk a6127fcd1a Provide sync watch rooms as argument rather than assuming all joined rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 6c724bbc2f Cleanup/optimize sliding sync types and arguments.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:08 +00:00
Jason Volk b1ea7b101d Split sliding-sync extensions into units.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 4baa25f66f Add presence ping to sliding-sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 227b77e58e Refactor sliding-sync e2ee extension.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 54057da84e ci: Fix artifact extraction argument for package.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 732825a390 ci: Publish verbose logging container builds to registries. (closes #181)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 04:11:57 +00:00
Jason Volk 6b74425f76 ci: Add integration memcheck to test matrix.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:22:02 +00:00
Jason Volk 74a576caf7 Add option to toggle compact log format.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 2b7cf7d5d5 Add optimized iteration of all room memberships for user.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 443248965d Add more optimized iterations for user memberships room_id's.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk ee777bc287 Rename user rooms iterations which have state vectors as values.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk aef38b1178 Rename userroomid/roomuserid table handles to reflect counter value.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 4743a8d968 Move args and runtime back to main from core (1313eb0b64).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 53ab6742c8 Fix shutdown signalling on startup.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 46193de7e8 Create appservice user on registration.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 9253f46c80 ci: Dedup the nix package job with the other distro package jobs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 5c127b5abd Add is_false() to BoolExt.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 17f6f1a5a6 Device list updates for left users was previously deduplicated.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 75509d50ca Add shorteventid to pdu_id query; reorg related id query interface.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 1d14426018 Fix missing Send on generic stream and result items.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jimmy Brush 2b9563be67 Fix off-by-one in sync v5
Simplified sliding sync specifies ranges to be inclusive while rust ranges are
exclusive.
2025-10-05 18:28:05 -04:00
Jason Volk 68c4f60bb3 Implement stateless sync; erase all the sync tokens.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 15:07:06 +00:00
Jason Volk d95c3f126f Fix typos
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 15:07:06 +00:00
Jason Volk f1c2548807 Add PduCount value to userroomid/roomuserid_joined; move PduCount to argument for update_membership.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk eda45e445c Implement better fmt::Debug for pdu::Builder.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 049defe977 nix: Disable doChecks due to missing /etc/resolve.conf.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 3c073110b8 Update nix flake.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 8d6bfde5a0 Use insta and criterion for main integration test and benches respectively.
docker/ci: Separate integration and unit tests and benches jobs.

Add directives to remove db before/after integration tests are performed.

Split start/run/stop phases; add more granular smoketests.

Split main integration tests into units for isolation.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
dasha_uwuandJason Volk 43f0882d83 clear destination and overrides cache on request failure. (#186) 2025-10-02 05:29:48 +00:00
Jason Volk fed52d24e4 Implement additional Matches for RoomFilter and Filter.
Apply filter for rooms/not_rooms; sender filter for presence.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk e6c85c97c6 Fix names and types misrepresenting PduCount as ShortEventId.
Add get_shorteventid_from_pdu_id() conversion.

Fix prev/next nearest-state interface (dev branch 642086ecfcfa).

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 368ead20a6 Add then_none() to BoolExt; move unsorted then_ utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk a803b84b27 Add missing reverse query for shortroomid => room_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 1058fbe9a7 Use specific key,val buffer sizes for append_to_state (no change to correctness).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk ae4aad3641 Add set difference for sorted streams util.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 95435ffe98 Fix unconnected inspect_log traits in Result utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 63e2cfa21b Rename core integration test directory for state_res.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 9383922d09 Use trace log level for multi-line table factory options on rocksdb startup.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk ae52676e33 Add back the default database path.
Allow default server_name when testing.

Default to smoke test vector when testing.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:45 +00:00
Jason Volk 292b601755 Fix possibly dependent conditions for server shutdown in config worker.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:00 +00:00
Jason Volk 1313eb0b64 Reorganize main crate for testability.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:28:58 +00:00
Jason Volk ba12773a5a Optimize force_state query pattern.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 83afe81f60 Move shortstatehash queries from state_accessor to state service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk f2740822e2 De-indent rooms state service definitions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 2417764771 Fix redundant prefix in admin query raw command suite.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 41ff81f843 Add handler_prefix to admin_command_dispatch for method dedup purposes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk fbcf4ba4f3 ci: Support job masking directives in commit message. [ci only it]
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason VolkandHashbrown777 af3cdf9263 Add script to docs for available optimization levels.
Co-authored-by: Hashbrown777 <hashbrown777@gmail.com>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 9d9ace1452 Add timeline interface to get next or prev pdu nearest to a pduid.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 83d64e0879 Implement Deserialize for RawPduId for database convenience.
Add room equality convenience on RawPduId.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 4cae17e83d Fix missing Send bound for Future.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 1c6992ccd4 Add option to bypass dropped column erasure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 89be6dc097 Switch to Criterion for benchtests.
ci: Add benches to workflow.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk a47f8f8a82 Rename complement test results file.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk aac5006bf5 ci: Add nix build w/ smoketest. (closes #111)
docker: Add target for nix build. (#111)

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 14:38:23 +00:00
Jason Volk 6d3ed09a2b ci: Add matrix-rust-sdk integration testing.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 14:38:23 +00:00
Jason Volk 377b7166f0 Bump Ruma.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 12:25:08 +00:00
Jason Volk 85b3de055d Broadcast send_join concurrent to other operations; pre-gather state concurrently.
Broadcast send_knock concurrently.

Concurrent access check for fed event.

Concurrent gather for state responses.

Populate room_version for format_pdu to elide repeated queries.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 02:32:45 +00:00
Jason Volk b9c790326a docker: Consolidate distro package fetches in base builder image.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-24 08:20:54 +00:00
Jason Volk 4e5910471b Require stronger ordering to sample final counter values on shutdown.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-24 08:20:54 +00:00
Jason Volk c3bc8c14f7 Make hickory system_conf error more useful. (resolves #179)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 21:02:37 +00:00
Jason Volk ac3ceb1b95 Modernize federation profile query route.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 19:48:07 +00:00
Jason Volk aa37e32471 Optimize Pdu prev_events and auth_events containers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 18:16:24 +00:00
Jason Volk 887a22dabd Fix docs regarding optimized builds; replace haswell with v1/v2/v3 etc. (fixes #175)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 18:16:24 +00:00
Jason Volk 6dca02860c Allow clippy::needless-update.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 11:53:39 +00:00
Jason Volkanddasha_uwu 86103183b3 Remove unused utils.
Co-authored-by: dasha_uwu <dasha@linuxping.win>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 09:02:35 +00:00
Jason Volk 8f4cc87051 Query rooms in server_sees_user() concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 14721c90c9 Fetch presence avatar_url and displayname concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 6d3b2d864f Simplify keys_changed iteration using room_keys_changed() upper-bound.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 7cf246eb73 Support dot paths for -O/--option command-line args. (fixes #162) (fixes #167)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
Jason Volk bffb7f89c8 Provide better generated config defaults for ldap section.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
Jason Volk 5467c9e486 Add option to redirect logging to stderr. (resolves #154)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 89a67af607 pull stuff out of globals 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 6bb101ac51 add local alias check 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 1c0b4e94ac fix spec violation and slight alias resolution refactor 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 9c4d376bec pass args by ref 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk d6e95c51c0 current_exe changes
remove unsafe

remove incorrect doc
2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk d5ce4b316f remove unnecessary as_ref 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 8f07a6c60f remove unused macros 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk b882e7efdb rename db to engine 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk e0169e3dca presence refactor 2025-09-23 05:16:29 +00:00
Jason Volk fe46755418 Remove declaration for Room V1 until support is confirmed complete. (#12)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk bbac80d2e6 ci: Fix pages deployment. (#165)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk a31c9b0c62 Fix documented examples of delegation. (fixes #165)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk cf4e65c607 Fix alternative resolver minimum TTL configuration. (fixes #176)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk f32b6ae17d Abstract and simplify serde_json round-trip to Ruma Raw.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 27222f23d2 Macroize several legacy errors.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk ed324d5972 Update device_signing/upload UIAA (fixes #169).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 69075b166f Add config option to toggle dns case randomization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk e3a711482a Update assertion for serde crate reorg to serde_core.
Show the deserialize_any type which fails the assertion.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 6759187b37 Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-22 20:44:25 +00:00
Guido GüntherandJason Volk 83ed29eb65 example config: Document support items
Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-09-22 20:44:25 +00:00
Guido Günther 6a685b7ee9 packaging: Move unit alias to install section
Otherwise there will be

  systemd[1]: /usr/lib/systemd/system/tuwunel.service:5: Unknown key 'Alias' in section [Unit], ignoring.

on startup. See `systemd.unit(5)`.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-09-15 19:53:05 +02:00
266 changed files with 7150 additions and 4665 deletions
+35 -9
View File
@@ -40,12 +40,16 @@ jobs:
machines: ${{vars.MACHINES || env.default_machines}}
package: ${{vars.PACKAGE || !contains(github.ref, 'refs/pull/')}}
publish: ${{vars.PUBLISH || !contains(github.ref, 'refs/pull/')}}
build_nix: ${{vars.BUILD_NIX || !contains(github.ref, 'refs/pull/')}}
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || contains(github.ref, 'tags/v')}}
check_pkgs: ${{vars.CHECK_PKGS || 'false'}}
complement: ${{vars.COMPLEMENT || 'true'}}
complement_runner: 'het'
docker_repo: ${{vars.DOCKER_REPO}}
release_url: ${{steps.release.outputs.upload_url}}
pages_url: 'https://matrix-construct.github.io/tuwunel/'
rust_sdk_integration: ${{vars.RUST_SDK_INTEGRATION || 'true'}}
head_msg: ${{github.event.head_commit.message || github.event.workflow_run.head_commit.message}}
strategy:
fail-fast: true
@@ -58,8 +62,8 @@ jobs:
- name: Initialize Builder
env:
runner: ${{matrix.runner}}
reserved_space: '{"het": "128GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "256GB", "aws": "64GB", "gcp": "192GB"}'
reserved_space: '{"het": "192GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "384GB", "aws": "64GB", "gcp": "192GB"}'
run: |
set +e
docker buildx inspect "${GITHUB_ACTOR}"
@@ -114,6 +118,8 @@ jobs:
if: >
!failure() && !cancelled()
&& !contains(github.ref, 'refs/tags/v')
&& !contains(needs.init.outputs.head_msg, '[ci no lint]')
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
name: Lint
needs: [init] #needs: [init, deps]
@@ -132,6 +138,7 @@ jobs:
{"cargo_profile": "test", "feat_set": "logging"},
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "bench"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "none"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "default"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "logging"},
@@ -161,11 +168,15 @@ jobs:
if: >
!failure() && !cancelled()
&& !contains(github.ref, 'refs/tags/v')
&& !contains(needs.init.outputs.head_msg, '[ci no test]')
name: Test
needs: [init, lint]
uses: ./.github/workflows/test.yml
with:
head_msg: ${{needs.init.outputs.head_msg}}
build_nix: ${{fromJSON(needs.init.outputs.build_nix)}}
rust_sdk_integration: ${{fromJSON(needs.init.outputs.rust_sdk_integration)}}
complement: ${{fromJSON(needs.init.outputs.complement)}}
complement_runner: ${{needs.init.outputs.complement_runner}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
@@ -212,14 +223,18 @@ jobs:
if: >
!failure() && !cancelled()
&& needs.init.outputs.package
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
&& !contains(needs.init.outputs.head_msg, '[ci no build]')
&& !contains(needs.init.outputs.head_msg, '[ci no package]')
name: Package
needs: [init, lint]
needs: [init, test]
uses: ./.github/workflows/package.yml
with:
release_url: ${{needs.init.outputs.release_url}}
check_pkgs: ${{needs.init.outputs.check_pkgs}}
build_pkgs: ${{needs.init.outputs.build_pkgs}}
build_nix: ${{fromJSON(needs.init.outputs.build_nix)}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
feat_sets: ${{needs.init.outputs.feat_sets}}
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
@@ -232,6 +247,7 @@ jobs:
[
{"feat_set": "none"},
{"cargo_profile": "test"},
{"cargo_profile": "bench"},
{"cargo_profile": "release-native"},
{"cargo_profile": "release-debuginfo", "feat_set": "default"},
{"cargo_profile": "release-debuginfo", "feat_set": "logging"},
@@ -244,6 +260,7 @@ jobs:
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "bake_target": "nix"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
@@ -254,18 +271,25 @@ jobs:
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v2-linux-gnu", "bake_target": "nix"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "bake_target": "nix"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "nix"},
]
publish:
if: >
!failure() && !cancelled()
&& needs.init.outputs.publish
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
&& !contains(needs.init.outputs.head_msg, '[ci no build]')
&& !contains(needs.init.outputs.head_msg, '[ci no package]')
&& !contains(needs.init.outputs.head_msg, '[ci no publish]')
name: Publish
needs: [init, test, package]
@@ -273,6 +297,7 @@ jobs:
with:
docker_repo: ${{needs.init.outputs.docker_repo}}
release_url: ${{needs.init.outputs.release_url}}
pages_url: ${{needs.init.outputs.pages_url}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
feat_sets: ${{needs.init.outputs.feat_sets}}
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
@@ -284,12 +309,13 @@ jobs:
excludes: >
[
{"feat_set": "none"},
{"feat_set": "logging"},
{"feat_set": "default"},
{"cargo_profile": "test"},
{"cargo_profile": "bench"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
@@ -298,11 +324,11 @@ jobs:
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
]
secrets:
+10 -1
View File
@@ -44,6 +44,9 @@ on:
check_pkgs:
type: string
default: 'false'
build_nix:
type: string
default: 'true'
jobs:
book:
@@ -155,7 +158,7 @@ jobs:
name: Distro Packages
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["deb", "rpm"]'
bake_targets: '["deb", "rpm", "nix"]'
cargo_profiles: '["release"]'
feat_sets: '["all"]'
rust_toolchains: '["stable"]'
@@ -176,6 +179,12 @@ jobs:
"rpm": {
"dst": "tuwunel.rpm",
"mime": "application/x-rpm"
},
"nix": {
"dst": "tuwunel.drv.tar.zst",
"src": "/opt/tuwunel/tuwunel.drv",
"mime": "application/zstd",
"zstd": 6
}
}
+9 -2
View File
@@ -44,20 +44,27 @@ on:
release_url:
type: string
description: For release assets
pages_url:
type: string
description: For pages deployment
jobs:
documents:
if: >
!failure() && !cancelled()
&& contains(fromJSON(inputs.sys_targets), fromJSON('["X64"]')[0])
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
&& !contains(github.ref, '-draft')
name: Documents
runs-on: ['X64', 'het']
permissions:
pages: write
contents: read
id-token: write
pages: write
environment:
name: github-pages
url: ${{inputs.pages_url}}
steps:
- id: book
+135 -6
View File
@@ -43,11 +43,20 @@ on:
default: '["all"]'
complement_runner:
type: string
rust_sdk_integration:
type: boolean
default: true
build_nix:
type: boolean
default: true
head_msg:
type: string
jobs:
docs:
if: >
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& 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])
@@ -69,7 +78,8 @@ jobs:
unit:
if: >
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& 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])
@@ -77,7 +87,7 @@ jobs:
name: Unit
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["unit"]'
bake_targets: '["unit", "integration"]'
cargo_profiles: '["test"]'
feat_sets: '["all"]'
rust_toolchains: ${{inputs.rust_toolchains}}
@@ -89,9 +99,57 @@ jobs:
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
bench:
if: >
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Bench
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["unit", "integration"]'
cargo_profiles: '["bench"]'
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v3-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
valgrind:
if: >
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Memcheck
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["integration-valgrind"]'
cargo_profiles: '["bench"]'
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v3-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
smoke:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci only it]')
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.cargo_profiles
&& inputs.machines
@@ -117,6 +175,8 @@ jobs:
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "test", "bake_target": "smoke-valgrind"},
{"cargo_profile": "bench"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"cargo_profile": "release", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "release", "bake_target": "smoke-valgrind"},
@@ -160,9 +220,77 @@ jobs:
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "smoke-valgrind"},
]
nix:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci only it]')
&& !contains(inputs.head_msg, '[ci no build]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
&& fromJSON(inputs.build_nix)
name: Smoke NixOS
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["smoke-nix"]'
cargo_profiles: '["release"]'
feat_sets: '["all"]'
rust_toolchains: '["stable"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
matrix-rust-sdk-integration:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.rust_sdk_integration
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.rust_targets), fromJSON('["x86_64-unknown-linux-gnu"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Matrix SDK Integration
needs: [smoke]
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["matrix-rust-sdk-integration"]'
cargo_profiles: ${{inputs.cargo_profiles}}
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: '["x86_64-unknown-linux-gnu"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: '["X64"]'
runner: ${{inputs.complement_runner}}
includes: ${{inputs.includes}}
artifact: >
{
"matrix-rust-sdk-integration": {
"src": "/var/log/tuwunel.log",
"dst": "matrix-rust-sdk-integration.tuwunel.log",
}
}
excludes: >
[
{"feat_set": "none"},
{"feat_set": "logging"},
{"cargo_profile": "release"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native"},
]
complement:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.complement
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.complement_feat_sets), fromJSON('["all"]')[0])
@@ -188,6 +316,7 @@ jobs:
compliance:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.complement
&& inputs.machines
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
@@ -196,7 +325,7 @@ jobs:
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Matrix Compliance
needs: [complement]
needs: [complement, smoke]
runs-on: ["${{matrix.machine}}", "${{inputs.complement_runner}}"]
concurrency:
group: complement-cant-walk-and-chew-bubblegum
@@ -242,7 +371,7 @@ jobs:
run: |
cid=$(cat "$name")
docker cp "$cid:/usr/src/complement/new_results.jsonl" tests/test_results/complement/test_results.jsonl
docker cp "$cid:/usr/src/complement/new_results.jsonl" tests/complement/results.jsonl
- if: success() || failure() && steps.execute.outcome == 'failure'
name: Upload New Results
@@ -250,7 +379,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: complement_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
path: ./tests/test_results/complement/test_results.jsonl
path: ./tests/complement/results.jsonl
- if: failure() && steps.execute.outcome == 'failure'
name: Upload Failure Output
Generated
+370 -268
View File
File diff suppressed because it is too large Load Diff
+32 -11
View File
@@ -29,7 +29,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-construct/tuwunel"
rust-version = "1.88.0"
version = "1.4.2"
version = "1.4.5"
[workspace.metadata.crane]
name = "tuwunel"
@@ -140,10 +140,19 @@ features = [
version = "0.8.3"
[workspace.dependencies.const-str]
version = "0.6"
version = "0.7"
[workspace.dependencies.criterion]
version = "0.7"
default-features = false
features = [
"cargo_bench_support",
"async_futures",
"async_tokio",
]
[workspace.dependencies.ctor]
version = "0.4"
version = "0.5"
[workspace.dependencies.cyborgtime]
version = "2.1"
@@ -263,7 +272,10 @@ features = ["std"]
[workspace.dependencies.nix]
version = "0.30"
default-features = false
features = ["resource"]
features = [
"resource",
"user",
]
[workspace.dependencies.num-traits]
version = "0.2"
@@ -308,7 +320,7 @@ default-features = false
[workspace.dependencies.ruma]
git = "https://github.com/matrix-construct/ruma"
rev = "5682b88cf1bcaf0f47805d614b476b242ef075d4"
rev = "b858d3478f2792a381463ef9b63f6c1469a5d81c"
features = [
"__compat",
"appservice-api-c",
@@ -338,9 +350,9 @@ features = [
"unstable-msc4133",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4310",
"unstable-msc4311",
"unstable-extensible-events",
"unstable-hydra",
]
[workspace.dependencies.rustls]
@@ -454,7 +466,7 @@ features = [
]
[workspace.dependencies.termimad]
version = "0.33"
version = "0.34"
default-features = false
[workspace.dependencies.thiserror]
@@ -690,7 +702,7 @@ inherits = "release-native.build-override"
#]
[profile.bench]
debug = "limited"
debug = false
strip = false
#rustflags = [
# "-Cremark=all",
@@ -711,7 +723,7 @@ strip = false
# and can be raised if build times are tolerable.
[profile.dev]
debug = "full"
debug = 0
#rustflags = [
# '--cfg', 'tuwunel_mods',
# '-Ztime-passes',
@@ -771,7 +783,7 @@ inherits = "dev"
[profile.dev.package.'*']
inherits = "dev"
debug = "limited"
debug = 0
incremental = false
codegen-units = 1
opt-level = 'z'
@@ -788,6 +800,11 @@ opt-level = 'z'
# '-Clink-arg=-Wl,-z,nodelete',
#]
# same as dev but slower.
[profile.dbg]
inherits = "dev"
debug = "full"
# primarily used for CI
[profile.test]
debug = "limited"
@@ -879,6 +896,9 @@ multiple_crate_versions = { level = "allow", priority = 1 }
###################
complexity = { level = "warn", priority = -1 }
# promotes forward-compat for literal ..default() construction
needless_update = { level = "allow", priority = 1 }
###################
correctness = { level = "warn", priority = -1 }
@@ -891,6 +911,7 @@ option_if_let_else = { level = "allow", priority = 1 } # TODO
redundant_pub_crate = { level = "allow", priority = 1 } # TODO
significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
significant_drop_tightening = { level = "allow", priority = 1 } # TODO
tuple_array_conversions = { level = "allow", priority = 1 }
###################
pedantic = { level = "warn", priority = -1 }
@@ -905,6 +926,7 @@ if_then_some_else_none = { level = "allow", priority = 1 }
inline_always = { level = "allow", priority = 1 }
map_unwrap_or = { level = "allow", priority = 1 }
match_bool = { level = "allow", priority = 1 }
match_same_arms = { level = "allow", priority = 1 }
missing_docs_in_private_items = { level = "allow", priority = 1 }
missing_errors_doc = { level = "allow", priority = 1 }
missing_panics_doc = { level = "allow", priority = 1 }
@@ -950,7 +972,6 @@ pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
rest_pat_in_fully_bound_structs = "warn"
semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
+4 -52
View File
@@ -1,57 +1,9 @@
# Tuwunel 1.4.2
# Tuwunel 1.4.5
September 12, 2025
October 24, 2025
Users running maubot, neochat, or any client or bridge not excluded below should update to this patch as soon as possible to reduce unnecessary resource consumption. (see: Bug Fixes)
### New Features
- Requested by @alaviss an alternative DNS resolver has been implemented for use with appservices and other configured targets intended for local networks. This passthru performs minimal caching and cannot be used for federation. Enable with `dns_passthru_appservices = true` or specifying hosts in `dns_passthru_domains` (#158)
- Contributed by @tototomate123 a nifty experimental feature can disable push notifications when you're active on one device from being sent to others. This can be enabled with `suppress_push_when_active`. Please thank them when your pocket stops vibrating while chatting on your desktop! (#150)
- Thanks to a report by @DetermineAbsurd the `m.federate` field can be defaulted to false when creating a room using the new `federate_created_rooms` config option. (#151)
- At the request of @grinapo verbose logging builds are now bundled with this release. These builds are found with the feature-set `-logging-` which is otherwise similar to `-all-`. This contains more messages at all levels optimized away in other release modes; it comes at some performance penalty.
- JWT tokens can now be used for authentication on any endpoint which supports UIA. For example: an external forgot-password service can send a token to the `client/account/password` endpoint to reset a user's password. This feature was commissioned and made public by an enterprise sponsor.
### Enhancements
- Sliding-sync has been significantly refactored. Performance has massively increased with many bugs and compliance issues also fixed. Please be aware we are tracking an issue related to read-marker behavior in Element X. The 🟢 dot does not unconditionally clear at every touch. Whether this is a feature or a bug, or both, is being investigated for v1.5.
- Hydra backports are now enabled by default. The change should be completely transparent. If you do notice any increased load try to increase the `cache_capacity_modifier` above default.
- Room deletions now also purge synctokens which can be significant to the overall storage consumed by a room. Users who have already deleted rooms please be assured an update planned for v1.5 will deal with cleansing synctokens in general.
- Room version 1 and 2 support took a step forward, possibly working for some rooms but is not yet considered adequately supported and the ticket remains open. (#12)
- Thanks to @AreYouLoco for contributing an updated Kubernetes [Helm Chart](https://github.com/AreYouLoco/tuwunel-helm); link added to docs.
All deployments serving ElementX, Element Pro, SchildiChat Next, or any client using sliding-sync should upgrade to this patch for an improved experience.
### Bug Fixes
- **Special thanks to @frebib for investigating a bug which triggers the uploading of unnecessary encryption one-time-keys.** Running over ten maubot instances it became obvious after observing increased resources and laggy bot response. This update removes any excess keys for a device. Thanks to @duckbuster for confirming neochat is affected. Clients confirmed unaffected include: Element, Element X, Nheko. Fractal, Cinny, matrix-rust-sdk and matrix-js-sdk clients and bots are probably unaffected. Mautrix-based bridges are probably affected. Users of unaffected clients should still upgrade.
- Thanks @dasha_uwu for refactoring alias resolution logic with fixes to remain compatible with the upcoming element-web release. This was an incredibly valuable contribution which will spare all of us from impending grief; the kind of ahead-of-the-game initiative I don't think a project like this could exist without. (adadafa88f3)
- Room deletions now preserve a small number of records to properly synchronize with local clients and remote servers after the room vanishes. Prior behavior is maintained with a `--force` flag added to the command.
- Thanks @scvalex for once again cleaning up our mess after Nix found the github CI was not running doctests. Thank you for contributing the patch 🙏 (#152).
- Thanks @Tronde for reporting a broken link to the CoC in the mdbook documentation. (#155)
- Specification compliance required the `/joined_rooms` endpoint be restricted to current members rather than including past members. (4b49aaad53a)
- Specification compliance required state events be made visible to prior members of a room where `history_visibility=shared`. (86781522b68)
- The `limit` parameter to the `/context` endpoint is now divided with de facto compatibility (matrix-org/matrix-spec#2202)
- The room avatar in sliding sync is now computed with greater compliance to the specification (3deebeab78f). This builds off earlier work done by @tmayoff in (a340e6786db).
- The canonical alias for a room is considered invalid if the primary alias is missing or removed (7221d466ce8). This is a T&S concern and we encourage reports for any other contexts where this condition should be applied.
- Presence is no longer updated by the private read-receipt or read-marker paths, only public receipts.
### Deprecations
- Hardened Malloc support had to be removed after the build broke. We will gladly add support back upon request or contribution.
- Progress made for sliding-sync in 1.4.3 was effectively reverted by 1.4.4. This release puts sliding-sync back on track with further progress exceeding 1.4.3. This release is now shipping without any known issues. Please report all issues. **IMPORTANT:** Issues from prior releases (such as stuck or incorrect unread counts) benefit from clearing the app's cache to immediately resolve. Go to settings, tap the version number 7 times, then enter the menu to select `clear cache`.
+1 -1
View File
@@ -4,7 +4,6 @@ Wants=network-online.target
After=network-online.target
Documentation=https://tuwunel.chat/
RequiresMountsFor=/var/lib/private/tuwunel
Alias=matrix-tuwunel.service
[Service]
DynamicUser=yes
@@ -77,3 +76,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+1 -1
View File
@@ -2,7 +2,6 @@
Description=Tuwunel Matrix homeserver
Wants=network-online.target
After=network-online.target
Alias=matrix-tuwunel.service
Documentation=https://tuwunel.chat/
[Service]
@@ -64,3 +63,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+3
View File
@@ -16,6 +16,7 @@ ARG CARGO_TERM_VERBOSE=0
ARG RUST_BACKTRACE
ARG JEMALLOC_OVERRIDE
ARG ROCKSDB_LIB_DIR
ARG VALGRINDFLAGS=""
ARG CARGO_BUILD_RUSTFLAGS
ARG CARGO_PROFILE_TEST_DEBUG
ARG CARGO_PROFILE_TEST_INCREMENTAL
@@ -47,6 +48,7 @@ ENV CARGO_TERM_VERBOSE="${CARGO_TERM_VERBOSE}"
ENV RUST_BACKTRACE="${RUST_BACKTRACE}"
ENV JEMALLOC_OVERRIDE="${JEMALLOC_OVERRIDE}"
ENV ROCKSDB_LIB_DIR="${ROCKSDB_LIB_DIR}"
ENV VALGRINDFLAGS="${VALGRINDFLAGS}"
ENV CARGO_PROFILE_TEST_DEBUG="${CARGO_PROFILE_TEST_DEBUG}"
ENV CARGO_PROFILE_TEST_INCREMENTAL="${CARGO_PROFILE_TEST_INCREMENTAL}"
ENV CARGO_PROFILE_BENCH_DEBUG="${CARGO_PROFILE_BENCH_DEBUG}"
@@ -74,6 +76,7 @@ RUN \
--mount=type=cache,dst=${targ_targ_dir}/.fingerprint,id=${cargo_target_share}/fingerprint,sharing=locked \
<<EOF
set -eux
ulimit -n 65535
rustup run ${rust_toolchain} \
cargo ${cargo_cmd} \
--verbose \
+2 -30
View File
@@ -1,22 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS key-gen-base
ARG var_cache
ARG var_lib_apt
WORKDIR /
COPY --link --from=input . .
RUN \
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
--mount=type=cache,dst=/var/lib/apt,id=${var_lib_apt},sharing=locked \
<<EOF
set -eux
apt-get -y -U install --no-install-recommends openssl gawk
EOF
FROM key-gen-base AS key-gen
FROM input AS key-gen
WORKDIR /complement
COPY <<EOF v3.ext
@@ -62,12 +46,11 @@ RUN [ -f certificate.crt ] && [ -f private_key.pem ]
FROM scratch AS complement-config
WORKDIR /complement
COPY --from=key-gen /complement/* .
COPY --from=source /usr/src/tuwunel/tests/test_results/complement/test_results.jsonl old_results.jsonl
COPY --from=source /usr/src/tuwunel/tests/complement/results.jsonl old_results.jsonl
COPY <<EOF complement.toml
[global]
address = "0.0.0.0"
admin_room_notices = false
allow_check_for_updates = false
allow_device_name_federation = true
allow_guest_registration = true
allow_invalid_tls_certificates = true
@@ -89,7 +72,6 @@ COPY <<EOF complement.toml
log_thread_ids = true
media_compat_file_link = false
media_startup_check = true
only_query_trusted_key_servers = false
port = [8008, 8448]
prune_missing_media = true
query_trusted_key_servers_first = false
@@ -156,16 +138,6 @@ ARG complement_tags="conduwuit_blacklist"
ARG complement_tests="./tests/..."
ARG complement_run=".*"
WORKDIR /
RUN \
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
--mount=type=cache,dst=/var/lib/apt,id=${var_lib_apt},sharing=locked \
--mount=type=cache,dst=/go/pkg/mod/cache,sharing=locked \
<<EOF
set -eux
apt-get -y -U install --no-install-recommends golang-go jq
EOF
WORKDIR /usr/src
ADD https://github.com/matrix-construct/complement.git#403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac complement
+107
View File
@@ -0,0 +1,107 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS matrix-rust-sdk-integration
ARG sys_name
ARG sys_version
ARG rust_toolchain
ARG rust_target
ARG RUSTUP_HOME
ARG CARGO_HOME
ARG CARGO_TARGET
ARG MRSDK_TARGET_DIR="/usr/src/matrix-rust-sdk"
ARG mrsdk_profile="release"
ARG mrsdk_target_share="${MRSDK_TARGET_DIR}/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/${mrsdk_profile}/_shared_cache"
ARG mrsdk_test_args=""
ARG mrsdk_test_opts=""
ARG mrsdk_skip_list=""
WORKDIR /
COPY --link --from=input . .
WORKDIR /usr/src
ADD --link https://github.com/matrix-construct/matrix-rust-sdk.git#integration matrix-rust-sdk
WORKDIR /etc
COPY <<EOF tuwunel.toml
[global]
admin_room_notices = false
allow_device_name_federation = true
allow_guest_registration = true
allow_legacy_media = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
create_admin_room = false
ip_range_denylist = []
log = "debug,tuwunel=trace,h2=warn,hyper=warn"
log_colors = false
log_guest_registrations = false
log_span_events = "NONE"
log_thread_ids = true
media_compat_file_link = false
media_startup_check = true
query_trusted_key_servers_first = false
query_trusted_key_servers_first_on_join = false
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_paranoid_file_checks = true
rocksdb_recovery_mode = 0
trusted_servers = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
EOF
WORKDIR /usr/lib
COPY --link --from=install /usr/lib .
WORKDIR /usr/bin
COPY --link --from=install /usr/bin/tuwunel .
WORKDIR /usr/src/matrix-rust-sdk
ENV targ_dir="${MRSDK_TARGET_DIR}/${mrsdk_profile}"
ENV targ_targ_dir="${MRSDK_TARGET_DIR}/${rust_target}/${mrsdk_profile}"
ENV RUST_BACKTRACE="full"
ENV TUWUNEL_CONFIG="/etc/tuwunel.toml"
ENV TUWUNEL_DATABASE_PATH="/var/db/tuwunel"
ENV TUWUNEL_SERVER_NAME="localhost:6167"
ENV TUWUNEL_PORT="[6167]"
ENV HOMESERVER_URL="http://localhost:6167"
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=${targ_dir}/deps,id=${mrsdk_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${mrsdk_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${mrsdk_target_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${mrsdk_target_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${mrsdk_target_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/deps,id=${mrsdk_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/build,id=${mrsdk_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/examples,id=${mrsdk_target_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/incremental,id=${mrsdk_target_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/.fingerprint,id=${mrsdk_target_share}/fingerprint,sharing=locked \
<<EOF
set -eux
nohup /usr/bin/tuwunel 1> /var/log/tuwunel.log &
PID=$!; trap "sleep 10s; set +ex; kill -QUIT ${PID}; wait ${PID}" EXIT
sleep 3s
rustup run ${rust_toolchain} \
cargo test \
--locked \
--release \
--color always \
--features default \
--target "${rust_target}" \
--target-dir "${MRSDK_TARGET_DIR}" \
--package matrix-sdk-integration-testing \
${mrsdk_test_args} \
-- \
--test-threads 1 \
--color always \
${mrsdk_skip_list} \
${mrsdk_test_opts} \
;
EOF
+89
View File
@@ -0,0 +1,89 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS nix-base
ARG sys_name
ARG sys_version
ARG sys_target
WORKDIR /
COPY --link --from=input . .
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install > nix-install
sh ./nix-install --daemon
rm nix-install
EOF
FROM nix-base AS build-nix
ARG sys_name
ARG sys_version
ARG sys_target
WORKDIR /usr/src/tuwunel
COPY --link --from=source /usr/src/tuwunel .
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
nix-build \
--verbose \
--cores 0 \
--max-jobs $(nproc) \
--log-format raw \
.
cp -afRL --copy-contents result /opt/tuwunel
EOF
FROM build-nix AS smoke-nix
ARG sys_name
ARG sys_version
ARG sys_target
WORKDIR /
COPY --link --from=build-nix . .
WORKDIR /opt/tuwunel
ENV TUWUNEL_DATABASE_PATH="/tmp/tuwunel/smoketest.db"
ENV TUWUNEL_LOG="info"
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
bin/tuwunel \
-Otest='["smoke", "fresh"]' \
-Oserver_name=\"localhost\" \
EOF
FROM build-nix AS nix-pkg
ARG sys_name
ARG sys_version
ARG sys_target
WORKDIR /
COPY --link --from=build-nix . .
WORKDIR /usr/src/tuwunel
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
#TODO: extract derivation?
mkdir -p /opt/tuwunel
touch /opt/tuwunel/tuwunel.drv
EOF
+198 -38
View File
@@ -184,7 +184,6 @@ dynamic_libs = [
nightly_rustflags = [
"--cfg tokio_unstable",
"--cfg tuwunel_bench",
"--allow=unstable-features",
"-Z crate-attr=feature(test)",
"-Z enforce-type-length-limit",
@@ -240,7 +239,15 @@ group "tests" {
"docs",
"unit",
"smoke",
"integration",
"matrix-compliance",
]
}
group "matrix-compliance" {
targets = [
"complement",
"matrix-rust-sdk-integration",
]
}
@@ -408,7 +415,7 @@ target "complement-base" {
elem("complement-config", [sys_name, sys_version, sys_target])
]
contexts = {
input = elem("target:base", [sys_name, sys_version, sys_target])
input = elem("target:builder", [sys_name, sys_version, sys_target])
}
args = complement_args
}
@@ -429,6 +436,82 @@ target "complement-config" {
}
}
#
# Integration tests
#
group "integrations" {
targets = [
"matrix-rust-sdk-integration",
"integration",
]
}
target "matrix-rust-sdk-integration" {
name = elem("matrix-rust-sdk-integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("matrix-rust-sdk-integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=docker,compression=zstd,mode=max,compression-level=${zstd_image_compress_level}"]
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
target = "matrix-rust-sdk-integration"
dockerfile = "${docker_dir}/Dockerfile.matrix-rust-sdk"
matrix = cargo_rust_feat_sys
inherits = [
elem("rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
]
contexts = {
input = elem("target:rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
install = elem("target:install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
mrsdk_test_args=""
mrsdk_test_opts=""
}
}
target "integration-valgrind" {
name = elem("integration-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("integration-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
matrix = cargo_rust_feat_sys
inherits = [
elem("integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
VALGRINDFLAGS = "--error-exitcode=1 --exit-on-first-error=yes --undef-value-errors=no --leak-check=no"
cargo_cmd = "valgrind test"
cargo_args = "--test=*"
}
}
target "integration" {
name = elem("integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("integration", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
TUWUNEL_DATABASE_PATH = "/tmp/integration.test.db"
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (cargo_profile == "bench"?
"--no-fail-fast --bench=*": "--no-fail-fast --test=*"
)
}
}
#
# Smoke tests
#
@@ -437,11 +520,26 @@ group "smoke" {
targets = [
"smoke-version",
"smoke-startup",
#"smoke-nix",
#"smoke-valgrind",
#"smoke-perf",
]
}
target "smoke-nix" {
name = elem("smoke-nix", [sys_name, sys_version, sys_target])
tags = [
elem_tag("smoke-nix", [sys_name, sys_version, sys_target], "latest"),
]
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
dockerfile = "${docker_dir}/Dockerfile.nix"
target = "smoke-nix"
matrix = sys
inherits = [
elem("build-nix", [sys_name, sys_version, sys_target]),
]
}
target "smoke-valgrind" {
name = elem("smoke-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
@@ -514,6 +612,70 @@ target "tests-smoke" {
}
}
#
# Unit tests
#
target "unit-valgrind" {
name = elem("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
VALGRINDFLAGS = "--error-exitcode=1 --exit-on-first-error=yes --undef-value-errors=no --leak-check=no"
cargo_cmd = "valgrind test"
cargo_args = "--lib --bins"
}
}
target "unit" {
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (cargo_profile == "bench"?
"--no-fail-fast --lib": "--no-fail-fast --lib --bins"
)
}
}
target "docs" {
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = "test"
cargo_args = "--doc --no-fail-fast"
}
}
#
# Installation
#
@@ -678,6 +840,7 @@ target "install" {
group "pkg" {
targets = [
"nix",
"deb",
"rpm",
"deb-install",
@@ -789,47 +952,36 @@ target "build-deb" {
}
}
#
# Unit tests
#
target "unit" {
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
target "nix" {
name = elem("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
elem_tag("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
output = ["type=docker,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
target = "nix-pkg"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
elem("build-nix", [sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (rust_toolchain == "nightly"?
"--no-fail-fast --all-targets": "--no-fail-fast --bins --tests"
)
}
}
target "docs" {
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
target "build-nix" {
name = elem("build-nix", [sys_name, sys_version, sys_target])
tags = [
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
elem_tag("build-nix", [sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
dockerfile = "${docker_dir}/Dockerfile.nix"
target = "build-nix"
matrix = sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
elem("builder", [sys_name, sys_version, sys_target]),
elem("source", [sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = "test"
cargo_args = "--doc --no-fail-fast"
input = elem("target:builder", [sys_name, sys_version, sys_target]),
source = elem("target:source", [sys_name, sys_version, sys_target]),
}
}
@@ -922,7 +1074,7 @@ target "build-tests" {
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = "--no-run"
cargo_args = (cargo_profile == "bench"? "--no-run --benches": "--no-run --tests")
}
}
@@ -1210,8 +1362,8 @@ target "deps-base" {
CARGO_PROFILE_TEST_DEBUG = "false"
CARGO_PROFILE_TEST_INCREMENTAL = "false"
CARGO_PROFILE_BENCH_DEBUG = "limited"
CARGO_PROFILE_BENCH_LTO = "false"
CARGO_PROFILE_BENCH_DEBUG = "false"
CARGO_PROFILE_BENCH_LTO = "thin"
CARGO_PROFILE_RELEASE_LTO = "thin"
CARGO_PROFILE_RELEASE_DEBUGINFO_DEBUG = "limited"
CARGO_PROFILE_RELEASE_DEBUGINFO_LTO = "off"
@@ -1238,7 +1390,7 @@ target "deps-base" {
"-C link-arg=-l:libgcc.a": "",
]):
cargo_profile == "release" && rust_toolchain == "nightly"?
(cargo_profile == "release" || cargo_profile == "bench") && rust_toolchain == "nightly"?
join(" ", [
join(" ", rustflags),
join(" ", nightly_rustflags),
@@ -1260,7 +1412,7 @@ target "deps-base" {
"-C link-arg=-l:libgcc.a": "",
]):
cargo_profile == "release" || cargo_profile == "release-debuginfo"?
cargo_profile == "release" || cargo_profile == "release-debuginfo" || cargo_profile == "bench"?
join(" ", [
join(" ", rustflags),
join(" ", static_rustflags),
@@ -1523,11 +1675,12 @@ rustup_components = [
]
cargo_installs = [
"cargo-chef",
"cargo-audit",
"cargo-deb",
#"cargo-arch",
"cargo-chef",
"cargo-deb",
"cargo-generate-rpm",
"cargo-valgrind",
#"lychee",
"mdbook",
"typos-cli",
@@ -1611,13 +1764,20 @@ kitchen_packages = [
"clang",
"cmake",
"curl",
"gawk",
"git",
"golang-go",
"gzip",
"jq",
"libc6-dev",
"libssl-dev",
"libsqlite3-dev",
"make",
"nix-bin",
"openssl",
"pkg-config",
"pkgconf",
"valgrind",
"xz-utils",
]
+1 -1
View File
@@ -88,7 +88,7 @@ extract_output() {
}
result_src="$cid:/usr/src/complement/new_results.jsonl"
result_dst="tests/test_results/complement/test_results.jsonl"
result_dst="tests/complement/results.jsonl"
extract_results() {
docker cp "$result_src" "$result_dst"
}
+17 -18
View File
@@ -12,24 +12,23 @@ ### Static prebuilt binary
You may simply download the binary that fits your machine architecture (x86_64
or aarch64). Run `uname -m` to see what you need.
Prebuilt fully static musl binaries can be downloaded from the latest tagged
Prebuilt fully static binaries can be downloaded from the latest tagged
release [here](https://github.com/matrix-construct/tuwunel/releases/latest) or
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
packages.
`main` CI branch workflow artifact output. These also include `.deb` packages
for Debian or Ubuntu and `.rpm` packages for Red Hat or Fedora.
These can be curl'd directly from. `ci-bins` are CI workflow binaries by commit
hash/revision, and `releases` are tagged releases. Sort by descending last
modified for the latest.
For the **best** performance; if using an `x86_64` CPU made in the last ~10 years,
we recommend using the `-v3-` optimised packages. See below for a command to check
what your system supports. If the server refuses to start or exits with an "Illegal
Instruction" error you will need `-v2-` or `-v1-` packages instead. The database
backend, RocksDB, benefits from `-v2-` or greater as it features performance
critical hardware accelerated CRC32 hashing/checksumming.
These binaries have jemalloc and io_uring statically linked and included with
them, so no additional dynamic dependencies need to be installed.
For the **best** performance; if using an `x86_64` CPU made in the last ~15 years,
we recommend using the `-haswell-` optimised binaries. This sets
`-march=haswell` which is the most compatible and highest performance with
optimised binaries. The database backend, RocksDB, most benefits from this as it
will then use hardware accelerated CRC32 hashing/checksumming which is critical
for performance.
Linux users can run this script to display which optimization levels they may
choose:
```
cat /proc/cpuinfo | grep -Po '(avx|sse)[235]' | sort -u | sed 's/avx5/v4/;s/avx2/v3/;s/sse3/v2/;s/sse2/v1/' | sort
```
### Compiling
@@ -167,7 +166,7 @@ ### Caddy
### Other Reverse Proxies
As we would prefer our users to use Caddy, we will not provide configuration files for other proxys.
_Specific contributions for other proxies are welcome!_
You will need to reverse proxy everything under following routes:
- `/_matrix/` - core Matrix C-S and S-S APIs
@@ -187,8 +186,8 @@ ### Other Reverse Proxies
- [`/.well-known/matrix/support`](https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixsupport)
Examples of delegation:
- <https://puppygock.gay/.well-known/matrix/server>
- <https://puppygock.gay/.well-known/matrix/client>
- <https://matrix.org/.well-known/matrix/server>
- <https://matrix.org/.well-known/matrix/client>
For Apache and Nginx there are many examples available online.
Generated
+88 -132
View File
@@ -10,11 +10,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1748532342,
"narHash": "sha256-CvaKOUq8G10sghKpZhEB2UYjJoWhEkrDFggDgi7piUI=",
"lastModified": 1758711588,
"narHash": "sha256-0nZlCCDC5PfndsQJXXtcyrtrfW49I3KadGMDlutzaGU=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "ce9373715fe3fac7a174a65a7e6d6baeba8cb4f9",
"rev": "12cbeca141f46e1ade76728bce8adc447f2166c6",
"type": "github"
},
"original": {
@@ -29,14 +29,14 @@
"devenv": "devenv",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"lastModified": 1756385612,
"narHash": "sha256-+NU5MMhuPHHRyvZZWNFG7zt+leRSPsJu1MwhOUzkPUk=",
"owner": "cachix",
"repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"rev": "dc24688cd67518c3711d511fa369c0f5a131063a",
"type": "github"
},
"original": {
@@ -58,16 +58,21 @@
],
"git-hooks": [
"cachix",
"devenv"
"devenv",
"git-hooks"
],
"nixpkgs": "nixpkgs_2"
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1744206633,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"owner": "cachix",
"repo": "cachix",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"type": "github"
},
"original": {
@@ -80,11 +85,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1741891349,
"narHash": "sha256-YvrzOWcX7DH1drp5SGa+E/fc7wN3hqFtPbqPjZpOu1Q=",
"lastModified": 1756358878,
"narHash": "sha256-H/4TkbFcGsMRDZ7gNaXZ7NxeO3b1YgkVt9zPb+3ATSg=",
"owner": "matrix-construct",
"repo": "complement",
"rev": "e587b3df569cba411aeac7c20b6366d03c143745",
"rev": "403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac",
"type": "github"
},
"original": {
@@ -95,18 +100,12 @@
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"type": "github"
},
"original": {
@@ -117,11 +116,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1748970125,
"narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=",
"lastModified": 1758758545,
"narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=",
"owner": "ipetkov",
"repo": "crane",
"rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
"rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364",
"type": "github"
},
"original": {
@@ -149,11 +148,11 @@
]
},
"locked": {
"lastModified": 1748273445,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
"lastModified": 1754404745,
"narHash": "sha256-BdbW/iTImczgcuATgQIa9sPGuYIBxVq2xqcvICsa2AQ=",
"owner": "cachix",
"repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"rev": "6563b21105168f90394dfaf58284b078af2d7275",
"type": "github"
},
"original": {
@@ -170,11 +169,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1749883145,
"narHash": "sha256-RlcGw3vAnbI3cfZn8aFaovNUd7312VZh+/FDWkqdA7E=",
"lastModified": 1759251341,
"narHash": "sha256-0vt4IQnTLyAhaeom3h9GOCpO2+av+wK4zP7O8BSKr6I=",
"owner": "nix-community",
"repo": "fenix",
"rev": "a804172f150bcf81262655324e583bb0cd0f28dd",
"rev": "af8a7505a1c62fab493157ea380a05c094bb63af",
"type": "github"
},
"original": {
@@ -187,11 +186,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@@ -241,11 +240,11 @@
]
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
@@ -264,11 +263,11 @@
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
@@ -309,11 +308,11 @@
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"lastModified": 1750779888,
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
"type": "github"
},
"original": {
@@ -344,30 +343,14 @@
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"liburing": {
"flake": false,
"locked": {
"lastModified": 1749816983,
"narHash": "sha256-p5hXfDe53Y4MVwL2+wKZYpy4OPGvqFFnOEvkMsFAO6c=",
"lastModified": 1759268303,
"narHash": "sha256-nT3JWey0P2lPWlnwN57XHmuzkeW8Mo795+Ahf8S5TMI=",
"owner": "axboe",
"repo": "liburing",
"rev": "ad83d3ab64894c16eaf21ef869656a5bddb93ca4",
"rev": "1671cb952a8f680214b04ae9546d4f649f1b367b",
"type": "github"
},
"original": {
@@ -381,11 +364,20 @@
"inputs": {
"flake-compat": [
"cachix",
"devenv"
"devenv",
"flake-compat"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"git-hooks-nix": [
"cachix",
"devenv",
"git-hooks"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-23-11": [
"cachix",
"devenv"
@@ -393,34 +385,30 @@
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1745930071,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
"owner": "domenkozar",
"lastModified": 1752773918,
"narHash": "sha256-dOi/M6yNeuJlj88exI+7k154z+hAhFcuB8tZktiW7rg=",
"owner": "cachix",
"repo": "nix",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
"rev": "031c3cf42d2e9391eee373507d8c12e0f9606779",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"owner": "cachix",
"ref": "devenv-2.30",
"repo": "nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"lastModified": 1757882181,
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
"type": "github"
},
"original": {
@@ -438,11 +426,11 @@
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"type": "github"
},
"original": {
@@ -453,11 +441,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1726042813,
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"type": "github"
},
"original": {
@@ -469,27 +457,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"lastModified": 1751741127,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"rev": "29e290002bfff26af1db6f64d070698019460302",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"lastModified": 1754214453,
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
"type": "github"
},
"original": {
@@ -501,43 +489,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"lastModified": 1759070547,
"narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1749871736,
"narHash": "sha256-K9yBph93OLTNw02Q6e9CYFGrUhvEXnh45vrZqIRWfvQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6afe187897bef7933475e6af374c893f4c84a293",
"rev": "647e5c14cbd5067f44ac86b74f014962df460840",
"type": "github"
},
"original": {
@@ -575,18 +531,18 @@
"flake-utils": "flake-utils",
"liburing": "liburing",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5",
"nixpkgs": "nixpkgs_3",
"rocksdb": "rocksdb"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1749829309,
"narHash": "sha256-t6x6/PKg8Shnkd3htrxf3WMgycfRLRWvN9JHAmGWf+s=",
"lastModified": 1759134797,
"narHash": "sha256-YPi+jL3tx/yC5J5l7/OB7Lnlr9BMTzYnZtm7tRJzUNg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "a497f4114ccf24978accb56190e60d1e1659e0c7",
"rev": "062ac7a5451e8e92a32e22a60d86882d6a034f3f",
"type": "github"
},
"original": {
+4 -1
View File
@@ -129,6 +129,7 @@ buildDepsOnlyEnv =
buildPackageEnv = {
TUWUNEL_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
TUWUNEL_DATABASE_PATH = "/var/tmp/tuwunel.db";
} // buildDepsOnlyEnv // {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
@@ -201,7 +202,9 @@ craneLib.buildPackage ( commonAttrs // {
env = buildDepsOnlyEnv;
});
doCheck = true;
# Disabled due to integration test failing to find /etc/resolv.conf
doCheck = false;
doBenchmark = false;
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
+1 -1
View File
@@ -2,7 +2,6 @@
Description=Tuwunel Matrix homeserver
Wants=network-online.target
After=network-online.target
Alias=matrix-tuwunel.service
Documentation=https://tuwunel.chat/
[Service]
@@ -63,3 +62,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+1
View File
@@ -11,6 +11,7 @@ version.workspace = true
[lib]
path = "mod.rs"
bench = false
crate-type = [
"rlib",
# "dylib",
+6 -17
View File
@@ -28,10 +28,7 @@
},
warn,
};
use tuwunel_service::rooms::{
short::{ShortEventId, ShortRoomId},
state_compressor::HashSetCompressStateEvent,
};
use tuwunel_service::rooms::{short::ShortRoomId, state_compressor::HashSetCompressStateEvent};
use crate::admin_command;
@@ -138,16 +135,8 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
}
#[admin_command]
pub(super) async fn get_short_pdu(
&self,
shortroomid: ShortRoomId,
shorteventid: ShortEventId,
) -> Result {
let pdu_id: RawPduId = PduId {
shortroomid,
shorteventid: shorteventid.into(),
}
.into();
pub(super) async fn get_short_pdu(&self, shortroomid: ShortRoomId, count: i64) -> Result {
let pdu_id: RawPduId = PduId { shortroomid, count: count.into() }.into();
let pdu_json = self
.services
@@ -304,7 +293,7 @@ pub(super) async fn get_remote_pdu(
#[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.alias.resolve(&room).await?;
let room_id = self.services.alias.maybe_resolve(&room).await?;
let room_state: Vec<Raw<AnyStateEvent>> = self
.services
.state_accessor
@@ -918,7 +907,7 @@ pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32
let mut files: Vec<_> = self
.services
.db
.db
.engine
.file_list()
.collect::<Result<_>>()?;
@@ -1023,7 +1012,7 @@ pub(super) async fn resync_database(&self) -> Result {
self.services
.db
.db
.engine
.update()
.map_err(|e| err!("Failed to update from primary: {e:?}"))
}
+3 -3
View File
@@ -4,7 +4,7 @@
use clap::Subcommand;
use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName};
use tuwunel_core::Result;
use tuwunel_service::rooms::short::{ShortEventId, ShortRoomId};
use tuwunel_service::rooms::short::ShortRoomId;
use self::tester::TesterCommand;
use crate::admin_command_dispatch;
@@ -43,8 +43,8 @@ pub(super) enum DebugCommand {
/// Shortroomid integer
shortroomid: ShortRoomId,
/// Shorteventid integer
shorteventid: ShortEventId,
/// PduCount integer
count: i64,
},
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
+6 -1
View File
@@ -10,6 +10,7 @@
mod room_timeline;
mod sending;
mod short;
mod sync;
mod users;
use clap::Subcommand;
@@ -20,7 +21,7 @@
presence::PresenceCommand, pusher::PusherCommand, raw::RawCommand, resolver::ResolverCommand,
room_alias::RoomAliasCommand, room_state_cache::RoomStateCacheCommand,
room_timeline::RoomTimelineCommand, sending::SendingCommand, short::ShortCommand,
users::UsersCommand,
sync::SyncCommand, users::UsersCommand,
};
use crate::admin_command_dispatch;
@@ -76,6 +77,10 @@ pub(super) enum QueryCommand {
#[command(subcommand)]
Short(ShortCommand),
/// - sync service
#[command(subcommand)]
Sync(SyncCommand),
/// - raw service
#[command(subcommand)]
Raw(RawCommand),
+14 -14
View File
@@ -16,16 +16,16 @@
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[admin_command_dispatch(handler_prefix = "raw")]
#[derive(Debug, Subcommand)]
#[allow(clippy::enum_variant_names)]
/// Query tables from database
pub(crate) enum RawCommand {
/// - List database maps
RawMaps,
Maps,
/// - Raw database query
RawGet {
Get {
/// Map name
map: String,
@@ -38,7 +38,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database delete (for string keys)
RawDel {
Del {
/// Map name
map: String,
@@ -47,7 +47,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database keys iteration
RawKeys {
Keys {
/// Map name
map: String,
@@ -56,7 +56,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database key size breakdown
RawKeysSizes {
KeysSizes {
/// Map name
map: Option<String>,
@@ -65,7 +65,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database keys total bytes
RawKeysTotal {
KeysTotal {
/// Map name
map: Option<String>,
@@ -74,7 +74,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database values size breakdown
RawValsSizes {
ValsSizes {
/// Map name
map: Option<String>,
@@ -83,7 +83,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database values total bytes
RawValsTotal {
ValsTotal {
/// Map name
map: Option<String>,
@@ -92,7 +92,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database items iteration
RawIter {
Iter {
/// Map name
map: String,
@@ -101,7 +101,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database keys iteration
RawKeysFrom {
KeysFrom {
/// Map name
map: String,
@@ -114,7 +114,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database items iteration
RawIterFrom {
IterFrom {
/// Map name
map: String,
@@ -127,7 +127,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database record count
RawCount {
Count {
/// Map name
map: Option<String>,
@@ -165,7 +165,7 @@ pub(crate) enum RawCommand {
}
#[admin_command]
pub(super) async fn compact(
pub(super) async fn raw_compact(
&self,
map: Option<Vec<String>>,
start: Option<String>,
+22 -2
View File
@@ -74,6 +74,10 @@ pub(crate) enum RoomStateCacheCommand {
user_id: OwnedUserId,
room_id: OwnedRoomId,
},
UserMemberships {
user_id: OwnedUserId,
},
}
pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context<'_>) -> Result {
@@ -282,7 +286,7 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.state_cache
.rooms_invited(&user_id)
.rooms_invited_state(&user_id)
.collect()
.await;
let query_time = timer.elapsed();
@@ -297,7 +301,7 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.state_cache
.rooms_left(&user_id)
.rooms_left_state(&user_id)
.collect()
.await;
let query_time = timer.elapsed();
@@ -316,6 +320,22 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
.await;
let query_time = timer.elapsed();
context
.write_str(&format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))
.await
},
| RoomStateCacheCommand::UserMemberships { user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.state_cache
.all_user_memberships(&user_id)
.map(|(membership, room_id)| (membership, room_id.to_owned()))
.collect::<Vec<_>>()
.await;
let query_time = timer.elapsed();
context
.write_str(&format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
+10 -2
View File
@@ -25,7 +25,11 @@ pub(crate) enum RoomTimelineCommand {
#[admin_command]
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
let result = self
.services
@@ -43,7 +47,11 @@ pub(super) async fn pdus(
from: Option<String>,
limit: Option<usize>,
) -> Result {
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
+5 -1
View File
@@ -30,7 +30,11 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
#[admin_command]
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
let shortid = self
.services
+82
View File
@@ -0,0 +1,82 @@
use clap::Subcommand;
use ruma::{OwnedDeviceId, OwnedUserId};
use tuwunel_core::Result;
use tuwunel_service::sync::into_connection_key;
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Query sync service state
pub(crate) enum SyncCommand {
/// List sliding-sync connections.
ListConnections,
/// Show details of sliding sync connection by ID.
ShowConnection {
user_id: OwnedUserId,
device_id: OwnedDeviceId,
conn_id: Option<String>,
},
/// Drop connections for a user, device, or all.
DropConnections {
user_id: Option<OwnedUserId>,
device_id: Option<OwnedDeviceId>,
conn_id: Option<String>,
},
}
#[admin_command]
pub(super) async fn list_connections(&self) -> Result {
let connections = self.services.sync.list_loaded_connections().await;
for connection_key in connections {
self.write_str(&format!("{connection_key:?}\n"))
.await?;
}
Ok(())
}
#[admin_command]
pub(super) async fn show_connection(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
conn_id: Option<String>,
) -> Result {
let key = into_connection_key(user_id, device_id, conn_id);
let cache = self
.services
.sync
.get_loaded_connection(&key)
.await?;
let out;
{
let cached = cache.lock().await;
out = format!("{cached:#?}");
};
self.write_str(out.as_str()).await
}
#[admin_command]
pub(super) async fn drop_connections(
&self,
user_id: Option<OwnedUserId>,
device_id: Option<OwnedDeviceId>,
conn_id: Option<String>,
) -> Result {
self.services
.sync
.clear_connections(
user_id.as_deref(),
device_id.as_deref(),
conn_id.map(Into::into).as_ref(),
)
.await;
Ok(())
}
+5 -5
View File
@@ -48,7 +48,7 @@ pub(crate) enum RoomModerationCommand {
async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
debug!("Got room alias or ID: {}", room);
let admin_room_alias = &self.services.globals.admin_alias;
let admin_room_alias = &self.services.admin.admin_alias;
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
@@ -105,7 +105,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
@@ -209,7 +209,7 @@ async fn ban_list_of_rooms(&self) -> Result {
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>();
let admin_room_alias = &self.services.globals.admin_alias;
let admin_room_alias = &self.services.admin.admin_alias;
let mut room_ban_count: usize = 0;
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
@@ -260,7 +260,7 @@ async fn ban_list_of_rooms(&self) -> Result {
match self
.services
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
@@ -423,7 +423,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
+4 -4
View File
@@ -67,7 +67,7 @@ pub(super) async fn list_features(&self, available: bool, enabled: bool, comma:
#[admin_command]
pub(super) async fn memory_usage(&self) -> Result {
let services_usage = self.services.memory_usage().await?;
let database_usage = self.services.db.db.memory_usage()?;
let database_usage = self.services.db.engine.memory_usage()?;
let allocator_usage = tuwunel_core::alloc::memory_usage()
.map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
@@ -88,7 +88,7 @@ pub(super) async fn clear_caches(&self) -> Result {
pub(super) async fn list_backups(&self) -> Result {
self.services
.db
.db
.engine
.backup_list()?
.try_stream()
.try_for_each(|result| write!(self, "{result}"))
@@ -102,13 +102,13 @@ pub(super) async fn backup_database(&self) -> Result {
.services
.server
.runtime()
.spawn_blocking(move || match db.db.backup() {
.spawn_blocking(move || match db.engine.backup() {
| Ok(()) => "Done".to_owned(),
| Err(e) => format!("Failed: {e}"),
})
.await?;
let count = self.services.db.db.backup_count()?;
let count = self.services.db.engine.backup_count()?;
self.write_str(&format!("{result}. Currently have {count} backups."))
.await
}
+19 -7
View File
@@ -119,7 +119,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.is_empty()
{
for room in &self.services.server.config.auto_join_rooms {
let Ok(room_id) = self.services.alias.resolve(room).await else {
let Ok(room_id) = self.services.alias.maybe_resolve(room).await else {
error!(
%user_id,
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
@@ -408,7 +408,7 @@ pub(super) async fn force_join_list_of_local_users(
let (room_id, servers) = self
.services
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
if !self
@@ -532,7 +532,7 @@ pub(super) async fn force_join_all_local_users(
let (room_id, servers) = self
.services
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
if !self
@@ -617,7 +617,7 @@ pub(super) async fn force_join_room(
let (room_id, servers) = self
.services
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
assert!(
@@ -645,7 +645,11 @@ pub(super) async fn force_leave_room(
room_id: OwnedRoomOrAliasId,
) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
assert!(
self.services.globals.user_is_local(&user_id),
@@ -678,7 +682,11 @@ pub(super) async fn force_leave_room(
#[admin_command]
pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAliasId) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
assert!(
self.services.globals.user_is_local(&user_id),
@@ -744,7 +752,11 @@ pub(super) async fn force_promote(
room_id: OwnedRoomOrAliasId,
) -> Result {
let target_id = parse_user_id(self.services, &target_id)?;
let room_id = self.services.alias.resolve(&room_id).await?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
let state_lock = self.services.state.mutex.lock(&room_id).await;
+1
View File
@@ -11,6 +11,7 @@ version.workspace = true
[lib]
path = "mod.rs"
bench = false
crate-type = [
"rlib",
# "dylib",
+6 -8
View File
@@ -5,7 +5,7 @@
OwnedServerName, RoomAliasId, RoomId,
api::client::alias::{create_alias, delete_alias, get_alias},
};
use tuwunel_core::{Err, Result, debug};
use tuwunel_core::{Err, Result, debug, err};
use tuwunel_service::Services;
use crate::Ruma;
@@ -26,8 +26,8 @@ pub(crate) async fn create_alias_route(
// this isn't apart of alias_checks or delete alias route because we should
// allow removing forbidden room aliases
if services
.globals
.forbidden_alias_names()
.config
.forbidden_alias_names
.is_match(body.room_alias.alias())
{
return Err!(Request(Forbidden("Room alias is forbidden.")));
@@ -83,13 +83,11 @@ pub(crate) async fn get_alias_route(
) -> Result<get_alias::v3::Response> {
let room_alias = body.body.room_alias;
let Ok((room_id, servers)) = services
let (room_id, servers) = services
.alias
.resolve_alias(&room_alias, None)
.resolve_alias(&room_alias)
.await
else {
return Err!(Request(NotFound("Room with alias not found.")));
};
.map_err(|_| err!(Request(NotFound("Room with alias not found."))))?;
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
+1 -1
View File
@@ -134,7 +134,7 @@ pub(crate) async fn get_context_route(
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.state_accessor
.state
.pdu_shortstatehash(state_at)
.or_else(|_| services.state.get_room_shortstatehash(room_id))
.map_ok(|shortstatehash| {
+39 -61
View File
@@ -6,14 +6,10 @@
CanonicalJsonObject, CanonicalJsonValue, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId,
UserId,
api::{
client::{
error::ErrorKind,
keys::{
claim_keys, get_key_changes, get_keys, upload_keys,
upload_signatures::{self},
upload_signing_keys,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
client::keys::{
claim_keys, get_key_changes, get_keys, upload_keys,
upload_signatures::{self},
upload_signing_keys,
},
federation,
},
@@ -21,11 +17,12 @@
serde::Raw,
};
use serde_json::json;
use tuwunel_core::{Err, Error, Result, debug, debug_warn, err, result::NotFound, utils};
use tuwunel_core::{
Err, Result, debug, debug_error, debug_warn, err, result::NotFound, utils::json,
};
use tuwunel_service::{Services, users::parse_master_key};
use super::SESSION_ID_LENGTH;
use crate::Ruma;
use crate::{Ruma, router::auth_uiaa};
/// # `POST /_matrix/client/r0/keys/upload`
///
@@ -162,23 +159,17 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> {
let (sender_user, sender_device) = body.sender();
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
..Default::default()
};
// Access token is required for this endpoint regardless of conditional UIAA so
// we'll always have a sender_user.
match check_for_new_keys(
services,
sender_user,
body.sender_user(),
body.self_signing_key.as_ref(),
body.user_signing_key.as_ref(),
body.master_key.as_ref(),
)
.await
.inspect_err(|e| debug!(?e))
.inspect_err(|e| debug_error!(?e))
{
| Ok(exists) => {
if let Some(result) = exists {
@@ -186,45 +177,25 @@ pub(crate) async fn upload_signing_keys_route(
// (lost connection for example)
return Ok(result);
}
debug!(
"Skipping UIA in accordance with MSC3967, the user didn't have any existing keys"
);
// Some of the keys weren't found, so we let them upload
debug!("Skipping UIA in accordance with MSC3967, user had no existing keys");
},
| _ => {
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body.as_ref() {
| Some(json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
},
},
}
let authed_user = auth_uiaa(&services, &body).await?;
assert_eq!(
body.sender_user(),
authed_user,
"Expected UIAA of {0} and not {authed_user}",
body.sender_user(),
);
},
}
services
.users
.add_cross_signing_keys(
sender_user,
body.sender_user(),
&body.master_key,
&body.self_signing_key,
&body.user_signing_key,
@@ -250,6 +221,7 @@ async fn check_for_new_keys(
.users
.get_master_key(None, user_id, &|_| true)
.await;
if result.is_not_found() {
empty = true;
} else {
@@ -262,6 +234,7 @@ async fn check_for_new_keys(
}
}
}
if let Some(user_signing_key) = user_signing_key {
let key = services.users.get_user_signing_key(user_id).await;
if key.is_not_found() && !empty {
@@ -269,6 +242,7 @@ async fn check_for_new_keys(
"Tried to update an existing user signing key, UIA required"
)));
}
if !key.is_not_found() {
let existing_signing_key = key?.deserialize()?;
if existing_signing_key != user_signing_key.deserialize()? {
@@ -278,17 +252,20 @@ async fn check_for_new_keys(
}
}
}
if let Some(self_signing_key) = self_signing_key {
let key = services
.users
.get_self_signing_key(None, user_id, &|_| true)
.await;
if key.is_not_found() && !empty {
debug!(?key);
debug_error!(?key);
return Err!(Request(Forbidden(
"Tried to add a new signing key independently from the master key"
)));
}
if !key.is_not_found() {
let existing_signing_key = key?.deserialize()?;
if existing_signing_key != self_signing_key.deserialize()? {
@@ -298,6 +275,7 @@ async fn check_for_new_keys(
}
}
}
if empty {
return Ok(None);
}
@@ -381,12 +359,12 @@ pub(crate) async fn get_key_changes_route(
let from = body
.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?;
.map_err(|_| err!(Request(InvalidParam("Invalid `from`."))))?;
let to = body
.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?;
.map_err(|_| err!(Request(InvalidParam("Invalid `to`."))))?;
device_list_updates.extend(
services
@@ -561,16 +539,16 @@ pub(crate) async fn get_keys_helper<F>(
.signatures
.append(&mut our_master_key.signatures);
}
let json = serde_json::to_value(master_key).expect("to_value always works");
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
// Dont notify. A notification would trigger another key request resulting in
// an endless loop.
let notify = false;
let raw = Some(json::to_raw(master_key)?);
services
.users
.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key
* request resulting in an endless loop */
)
.add_cross_signing_keys(&user, &raw, &None, &None, notify)
.await?;
if let Some(raw) = raw {
master_keys.insert(user.clone(), raw);
}
+5
View File
@@ -22,6 +22,7 @@
use tuwunel_core::{
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
matrix::{
PduCount,
event::{Event, gen_event_id},
pdu::{PduBuilder, PduEvent},
},
@@ -276,6 +277,7 @@ async fn knock_room_helper_local(
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Updating membership locally to knock state with provided stripped state events");
let count = services.globals.next_count();
services
.state_cache
.update_membership(
@@ -294,6 +296,7 @@ async fn knock_room_helper_local(
),
None,
false,
PduCount::Normal(*count),
)
.await?;
@@ -490,6 +493,7 @@ async fn knock_room_helper_remote(
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
let count = services.globals.next_count();
services
.state_cache
.update_membership(
@@ -508,6 +512,7 @@ async fn knock_room_helper_remote(
),
None,
false,
PduCount::Normal(*count),
)
.await?;
+12 -14
View File
@@ -2,7 +2,7 @@
use futures::{FutureExt, StreamExt, pin_mut};
use ruma::{
api::client::membership::{
get_member_events::{self, v3::MembershipEventFilter},
get_member_events::{self},
joined_members::{self, v3::RoomMember},
},
events::{
@@ -94,9 +94,7 @@ pub(crate) async fn joined_members_route(
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| {
membership_filter(pdu, Some(&MembershipEventFilter::Join), None)
})
.ready_filter_map(|pdu| membership_filter(pdu, Some(&MembershipState::Join), None))
.ready_filter_map(|pdu| {
let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
let sender = pdu.sender().to_owned();
@@ -115,22 +113,22 @@ pub(crate) async fn joined_members_route(
fn membership_filter<Pdu: Event>(
pdu: Pdu,
for_membership: Option<&MembershipEventFilter>,
not_membership: Option<&MembershipEventFilter>,
for_membership: Option<&MembershipState>,
not_membership: Option<&MembershipState>,
) -> Option<impl Event> {
let membership_state_filter = match for_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipEventFilter::Leave) => MembershipState::Leave,
| Some(MembershipState::Ban) => MembershipState::Ban,
| Some(MembershipState::Invite) => MembershipState::Invite,
| Some(MembershipState::Knock) => MembershipState::Knock,
| Some(MembershipState::Leave) => MembershipState::Leave,
| Some(_) | None => MembershipState::Join,
};
let not_membership_state_filter = match not_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Join) => MembershipState::Join,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipState::Ban) => MembershipState::Ban,
| Some(MembershipState::Invite) => MembershipState::Invite,
| Some(MembershipState::Join) => MembershipState::Join,
| Some(MembershipState::Knock) => MembershipState::Knock,
| Some(_) | None => MembershipState::Leave,
};
+1 -4
View File
@@ -165,10 +165,7 @@ async fn get_join_params(
},
// ... if room alias, resolve and don't shuffle ...
| Err(room_alias) => {
let (room_id, servers) = services
.alias
.resolve_alias(&room_alias, Some(via.to_vec()))
.await?;
let (room_id, servers) = services.alias.resolve_alias(&room_alias).await?;
(room_id, servers, Vec::new())
},
+11 -15
View File
@@ -46,13 +46,11 @@ pub(crate) async fn set_displayname_route(
.update_displayname(&body.user_id, body.displayname.clone(), &all_joined_rooms)
.await;
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await?;
Ok(set_display_name::v3::Response {})
}
@@ -148,14 +146,12 @@ pub(crate) async fn set_avatar_url_route(
)
.await;
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await
.ok();
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await
.ok();
Ok(set_avatar_url::v3::Response {})
}
+33 -34
View File
@@ -26,6 +26,12 @@ pub(crate) async fn set_read_marker_route(
) -> Result<set_read_marker::v3::Response> {
let sender_user = body.sender_user();
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
services
.user
.reset_notification_counts(sender_user, &body.room_id);
}
if let Some(event) = &body.fully_read {
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
content: ruma::events::fully_read::FullyReadEventContent { event_id: event.clone() },
@@ -39,23 +45,29 @@ pub(crate) async fn set_read_marker_route(
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event)?,
)
.await?;
.await
.ok();
}
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
if let Some(event) = &body.private_read_receipt {
let count = services
.timeline
.get_pdu_count(event)
.await
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
let PduCount::Normal(count) = count else {
return Err!(Request(InvalidParam(
"Event is a backfilled PDU and cannot be marked as read."
)));
};
services
.user
.reset_notification_counts(sender_user, &body.room_id);
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
}
if let Some(event) = &body.read_receipt {
if services.config.allow_local_presence {
services
.presence
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await?;
}
let receipt_content = BTreeMap::from_iter([(
event.to_owned(),
BTreeMap::from_iter([(
@@ -78,24 +90,12 @@ pub(crate) async fn set_read_marker_route(
},
)
.await;
}
if let Some(event) = &body.private_read_receipt {
let count = services
.timeline
.get_pdu_count(event)
.await
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
let PduCount::Normal(count) = count else {
return Err!(Request(InvalidParam(
"Event is a backfilled PDU and cannot be marked as read."
)));
};
services
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
.presence
.maybe_ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await
.ok();
}
Ok(set_read_marker::v3::Response {})
@@ -137,13 +137,6 @@ pub(crate) async fn create_receipt_route(
.await?;
},
| create_receipt::v3::ReceiptType::Read => {
if services.config.allow_local_presence {
services
.presence
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await?;
}
let receipt_content = BTreeMap::from_iter([(
body.event_id.clone(),
BTreeMap::from_iter([(
@@ -169,6 +162,12 @@ pub(crate) async fn create_receipt_route(
},
)
.await;
services
.presence
.maybe_ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await
.ok();
},
| create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services
+7 -7
View File
@@ -58,8 +58,8 @@ pub(crate) async fn get_register_available_route(
});
if services
.globals
.forbidden_usernames()
.config
.forbidden_usernames
.is_match(&body.username)
{
return Err!(Request(Forbidden("Username is forbidden")));
@@ -228,8 +228,8 @@ pub(crate) async fn register_route(
});
if services
.globals
.forbidden_usernames()
.config
.forbidden_usernames
.is_match(username)
&& !emergency_mode_enabled
{
@@ -384,8 +384,8 @@ pub(crate) async fn register_route(
// If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it
if !services
.globals
.new_user_displayname_suffix()
.config
.new_user_displayname_suffix
.is_empty()
&& body.appservice_info.is_none()
{
@@ -535,7 +535,7 @@ pub(crate) async fn register_route(
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
{
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.alias.resolve(room).await else {
let Ok(room_id) = services.alias.maybe_resolve(room).await else {
error!(
"Failed to resolve room alias to room ID when attempting to auto join \
{room}, skipping"
+5 -5
View File
@@ -678,8 +678,8 @@ async fn room_alias_check(
// check if room alias is forbidden
if services
.globals
.forbidden_alias_names()
.config
.forbidden_alias_names
.is_match(room_alias_name)
{
return Err!(Request(Unknown("Room alias name is forbidden.")));
@@ -725,8 +725,8 @@ async fn room_alias_check(
async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
// apply forbidden room alias checks to custom room IDs too
if services
.globals
.forbidden_alias_names()
.config
.forbidden_alias_names
.is_match(custom_room_id)
{
return Err!(Request(Unknown("Custom room ID is forbidden.")));
@@ -798,7 +798,7 @@ async fn can_create_room_check(
services: &Services,
body: &Ruma<create_room::v3::Request>,
) -> Result {
if !services.globals.allow_room_creation()
if !services.config.allow_room_creation
&& body.appservice_info.is_none()
&& !services.users.is_admin(body.sender_user()).await
{
+1 -1
View File
@@ -55,7 +55,7 @@ pub(crate) async fn get_room_summary(
) -> Result<get_summary::v1::Response> {
let (room_id, servers) = services
.alias
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
.maybe_resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
.await?;
if services.metadata.is_banned(&room_id).await {
+1 -1
View File
@@ -44,7 +44,7 @@ pub(crate) async fn get_hierarchy_route(
.as_ref()
.and_then(|s| PaginationToken::from_str(s).ok());
// Should prevent unexpeded behaviour in (bad) clients
// Should prevent unexpected behaviour in (bad) clients
if let Some(ref token) = key {
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
return Err!(Request(InvalidParam(
+2 -1
View File
@@ -195,6 +195,7 @@ async fn send_state_event_for_key_helper(
room_id,
&state_lock,
)
.boxed()
.await?;
Ok(event_id)
@@ -328,7 +329,7 @@ async fn allowed_to_send_state_event(
for alias in aliases {
let (alias_room_id, _servers) = services
.alias
.resolve_alias(&alias, None)
.resolve_alias(&alias)
.await
.map_err(|e| {
err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}")))
+6 -12
View File
@@ -1,13 +1,8 @@
mod v3;
mod v5;
use futures::{StreamExt, pin_mut};
use ruma::{
RoomId, UserId,
events::TimelineEventType::{
self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
},
};
use futures::{FutureExt, StreamExt, pin_mut};
use ruma::{RoomId, UserId};
use tuwunel_core::{
Error, PduCount, Result,
matrix::pdu::PduEvent,
@@ -17,9 +12,6 @@
pub(crate) use self::{v3::sync_events_route, v5::sync_events_v5_route};
pub(crate) const DEFAULT_BUMP_TYPES: &[TimelineEventType; 6] =
&[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
async fn load_timeline(
services: &Services,
sender_user: &UserId,
@@ -50,10 +42,12 @@ async fn load_timeline(
.by_ref()
.take(limit)
.collect()
.map(|mut pdus: Vec<_>| {
pdus.reverse();
pdus
})
.await;
let timeline_pdus: Vec<_> = timeline_pdus.into_iter().rev().collect();
// They /sync response doesn't always return all messages, so we say the output
// is limited unless there are events in non_timeline_pdus
let limited = non_timeline_pdus.next().await.is_some();
+204 -197
View File
@@ -5,8 +5,8 @@
use axum::extract::State;
use futures::{
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
future::{OptionFuture, join, join3, join4, join5, try_join3},
FutureExt, StreamExt, TryFutureExt,
future::{OptionFuture, join, join3, join4, join5},
pin_mut,
};
use ruma::{
@@ -36,7 +36,11 @@
};
use tokio::time;
use tuwunel_core::{
Result, at, err, error, extract_variant, is_equal_to,
Error, Result, at,
debug::INFO_SPAN_LEVEL,
err, error,
error::{inspect_debug_log, inspect_log},
extract_variant, is_equal_to,
matrix::{
Event,
event::Matches,
@@ -49,7 +53,6 @@
self, BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::{OptionStream, ReadyEqExt},
math::ruma_from_u64,
result::LogErr,
stream::{BroadbandExt, Tools, TryExpect, WidebandExt},
},
warn,
@@ -125,20 +128,16 @@ pub(crate) async fn sync_events_route(
) -> Result<sync_events::v3::Response, RumaResponse<UiaaResponse>> {
let (sender_user, sender_device) = body.sender();
// Presence update
if services.config.allow_local_presence {
services
.presence
.ping_presence(sender_user, &body.body.set_presence)
.await
.log_err()
.ok();
let ping_presence = services
.presence
.maybe_ping_presence(sender_user, &body.body.set_presence)
.inspect_err(inspect_log)
.ok();
// Record user as actively syncing for push suppression heuristic.
if services.config.suppress_push_when_active {
services.presence.note_sync(sender_user).await;
}
}
// Record user as actively syncing for push suppression heuristic.
let note_sync = services.presence.note_sync(sender_user);
join(ping_presence, note_sync).await;
let mut since = body
.body
@@ -164,10 +163,18 @@ pub(crate) async fn sync_events_route(
.expect("configuration must limit maximum timeout");
loop {
let watchers = services.sync.watch(sender_user, sender_device);
let next_batch = services.globals.wait_pending().await?;
let watch_rooms = services
.state_cache
.rooms_joined(sender_user)
.chain(services.state_cache.rooms_invited(sender_user));
let watchers = services
.sync
.watch(sender_user, sender_device, watch_rooms);
let next_batch = services.globals.wait_pending().await?;
debug_assert!(since <= next_batch, "next_batch is monotonic");
if since < next_batch || body.body.full_state {
let response = build_sync_events(&services, &body, since, next_batch).await?;
let empty = response.rooms.is_empty()
@@ -217,7 +224,7 @@ async fn build_empty_response(
#[tracing::instrument(
name = "build",
level = "debug",
level = INFO_SPAN_LEVEL,
ret(level = "trace"),
skip_all,
fields(
@@ -248,6 +255,7 @@ async fn build_sync_events(
let joined_rooms = services
.state_cache
.rooms_joined(sender_user)
.ready_filter(|&room_id| filter.room.matches(room_id))
.map(ToOwned::to_owned)
.broad_filter_map(|room_id| {
load_joined_room(
@@ -279,7 +287,8 @@ async fn build_sync_events(
let left_rooms = services
.state_cache
.rooms_left(sender_user)
.rooms_left_state(sender_user)
.ready_filter(|(room_id, _)| filter.room.matches(room_id))
.broad_filter_map(|(room_id, _)| {
handle_left_room(
services,
@@ -298,7 +307,8 @@ async fn build_sync_events(
let invited_rooms = services
.state_cache
.rooms_invited(sender_user)
.rooms_invited_state(sender_user)
.ready_filter(|(room_id, _)| filter.room.matches(room_id))
.fold_default(async |mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| {
let invite_count = services
.state_cache
@@ -321,7 +331,8 @@ async fn build_sync_events(
let knocked_rooms = services
.state_cache
.rooms_knocked(sender_user)
.rooms_knocked_state(sender_user)
.ready_filter(|(room_id, _)| filter.room.matches(room_id))
.fold_default(async |mut knocked_rooms: BTreeMap<_, _>, (room_id, knock_state)| {
let knock_count = services
.state_cache
@@ -345,7 +356,7 @@ async fn build_sync_events(
let presence_updates: OptionFuture<_> = services
.config
.allow_local_presence
.then(|| process_presence_updates(services, since, next_batch, sender_user))
.then(|| process_presence_updates(services, since, next_batch, sender_user, &filter))
.into();
let account_data = services
@@ -401,7 +412,7 @@ async fn build_sync_events(
// If the user doesn't share an encrypted room with the target anymore, we need
// to tell them
let device_list_left: HashSet<_> = left_encrypted_users
let device_list_left = left_encrypted_users
.into_iter()
.stream()
.broad_filter_map(async |user_id: OwnedUserId| {
@@ -424,8 +435,8 @@ async fn build_sync_events(
Ok(sync_events::v3::Response {
account_data: GlobalAccountData { events: account_data },
device_lists: DeviceLists {
left: device_list_left,
changed: device_list_updates.into_iter().collect(),
left: device_list_left.into_iter().collect(),
},
device_one_time_keys_count,
// Fallback keys are not yet supported
@@ -448,10 +459,12 @@ async fn process_presence_updates(
since: u64,
next_batch: u64,
syncing_user: &UserId,
filter: &FilterDefinition,
) -> PresenceUpdates {
services
.presence
.presence_since(since, Some(next_batch))
.ready_filter(|(user_id, ..)| filter.presence.matches(user_id))
.filter(|(user_id, ..)| {
services
.state_cache
@@ -466,6 +479,7 @@ async fn process_presence_updates(
})
.map(|(user_id, event)| (user_id.to_owned(), event.content))
.collect()
.boxed()
.await
}
@@ -538,9 +552,9 @@ async fn handle_left_room(
unsigned: None,
// The following keys are dropped on conversion
room_id: room_id.clone(),
prev_events: vec![],
prev_events: Default::default(),
auth_events: Default::default(),
depth: uint!(1),
auth_events: vec![],
redacts: None,
hashes: EventHash::default(),
signatures: None,
@@ -560,20 +574,22 @@ async fn handle_left_room(
let mut left_state_events = Vec::new();
let since_shortstatehash = services
.user
.get_token_shortstatehash(room_id, since);
.timeline
.prev_shortstatehash(room_id, PduCount::Normal(since).saturating_add(1))
.await
.ok();
let since_state_ids: HashMap<_, OwnedEventId> = since_shortstatehash
.map_ok(|since_shortstatehash| {
.map(|since_shortstatehash| {
services
.state_accessor
.state_full_ids(since_shortstatehash)
.map(Ok)
})
.try_flatten_stream()
.try_collect()
.await
.unwrap_or_default();
.into_iter()
.stream()
.flatten()
.collect()
.await;
let Ok(left_event_id): Result<OwnedEventId> = services
.state_accessor
@@ -585,7 +601,7 @@ async fn handle_left_room(
};
let Ok(left_shortstatehash) = services
.state_accessor
.state
.pdu_shortstatehash(&left_event_id)
.await
else {
@@ -664,12 +680,7 @@ async fn load_joined_room(
full_state: bool,
filter: &FilterDefinition,
) -> Result<(JoinedRoom, HashSet<OwnedUserId>, HashSet<OwnedUserId>)> {
let since_shortstatehash = services
.user
.get_token_shortstatehash(room_id, since)
.ok()
.map(Ok);
let initial = since == 0;
let timeline_limit: usize = filter
.room
.timeline
@@ -677,14 +688,38 @@ async fn load_joined_room(
.unwrap_or_else(|| uint!(10))
.try_into()?;
let timeline = load_timeline(
let (timeline_pdus, limited, last_timeline_count) = load_timeline(
services,
sender_user,
room_id,
PduCount::Normal(since),
Some(PduCount::Normal(next_batch)),
timeline_limit,
);
)
.await?;
let since_shortstatehash = services
.timeline
.prev_shortstatehash(room_id, PduCount::Normal(since).saturating_add(1))
.ok();
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
.first()
.map(at!(0))
.map(|count| {
services
.timeline
.get_shortstatehash(room_id, count)
.inspect_err(inspect_debug_log)
})
.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 receipt_events = services
.read_receipt
@@ -696,50 +731,32 @@ async fn load_joined_room(
.await
.or_some((read_user.to_owned(), edu))
})
.collect::<HashMap<OwnedUserId, Raw<AnySyncEphemeralRoomEvent>>>()
.map(Ok);
.collect::<HashMap<OwnedUserId, Raw<AnySyncEphemeralRoomEvent>>>();
let (since_shortstatehash, (timeline_pdus, limited, last_timeline_count), receipt_events) =
try_join3(since_shortstatehash, timeline, receipt_events)
.boxed()
.await?;
let encrypted_room = services.state_accessor.is_encrypted_room(room_id);
let horizon_shortstatehash: OptionFuture<_> = timeline_pdus
.iter()
.map(at!(0))
.map(PduCount::into_unsigned)
.map(|shorteventid| {
services
.state_accessor
.get_shortstatehash(shorteventid)
})
.next()
.into();
let (
(since_shortstatehash, horizon_shortstatehash, current_shortstatehash),
receipt_events,
encrypted_room,
) = join3(
join3(since_shortstatehash, horizon_shortstatehash, current_shortstatehash),
receipt_events,
encrypted_room,
)
.map(|((since, horizon, current), receipt, encrypted_room)| {
Ok::<_, Error>(((since, horizon.flat_ok(), current?), receipt, encrypted_room))
})
.boxed()
.await?;
let current_shortstatehash = services
.state_accessor
.get_shortstatehash(last_timeline_count.into_unsigned())
.or_else(|_| services.state.get_room_shortstatehash(room_id));
let lazy_load_options =
[&filter.room.state.lazy_load_options, &filter.room.timeline.lazy_load_options];
let (horizon_shortstatehash, current_shortstatehash) =
join(horizon_shortstatehash, current_shortstatehash)
.boxed()
.await;
let current_shortstatehash = current_shortstatehash
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))?;
let associate_token =
services
.user
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash);
let lazy_loading_enabled = filter.room.state.lazy_load_options.is_enabled()
|| filter
.room
.timeline
.lazy_load_options
.is_enabled();
let lazy_loading_enabled = !encrypted_room
&& lazy_load_options
.iter()
.any(|opts| opts.is_enabled());
let lazy_loading_context = &lazy_loading::Context {
user_id: sender_user,
@@ -749,8 +766,6 @@ async fn load_joined_room(
options: Some(&filter.room.state.lazy_load_options),
};
let initial = since == 0 || since_shortstatehash.is_none();
// Reset lazy loading because this is an initial sync
let lazy_load_reset: OptionFuture<_> = initial
.then(|| services.lazy_loading.reset(lazy_loading_context))
@@ -773,6 +788,22 @@ async fn load_joined_room(
})
.into();
let sender_joined_count = services
.state_cache
.get_joined_count(room_id, sender_user);
let since_encryption: OptionFuture<_> = since_shortstatehash
.map(|shortstatehash| {
services
.state_accessor
.state_get(shortstatehash, &StateEventType::RoomEncryption, "")
})
.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(|| {
@@ -782,39 +813,20 @@ async fn load_joined_room(
})
.into();
let since_sender_member: OptionFuture<_> = since_shortstatehash
.map(|short| {
services
.state_accessor
.state_get_content(short, &StateEventType::RoomMember, sender_user.as_str())
.ok()
})
.into();
let encrypted_room = services.state_accessor.is_encrypted_room(room_id);
let last_privateread_update = services
.read_receipt
.last_privateread_update(sender_user, room_id);
let (
(witness, since_sender_member),
(encrypted_room, ()),
(last_privateread_update, last_notification_read),
(sender_joined_count, since_encryption),
witness,
) = join3(
join(witness, since_sender_member),
join(encrypted_room, associate_token),
join(last_privateread_update, last_notification_read),
join(sender_joined_count, since_encryption),
witness,
)
.boxed()
.await;
let joined_since_last_sync =
since_sender_member
.flatten()
.is_none_or(|content: RoomMemberEventContent| {
content.membership != MembershipState::Join
});
let _encrypted_since_last_sync = !initial && encrypted_room && since_encryption.is_none();
let joined_since_last_sync = sender_joined_count.is_ok_and(|count| count > since);
let StateChanges {
heroes,
@@ -825,19 +837,15 @@ async fn load_joined_room(
services,
sender_user,
room_id,
full_state,
encrypted_room,
full_state || initial,
since_shortstatehash,
horizon_shortstatehash.flat_ok(),
horizon_shortstatehash,
current_shortstatehash,
joined_since_last_sync,
witness.as_ref(),
)
.await?;
let send_notification_counts =
last_notification_read.is_none_or(|last_count| last_count.gt(&since));
let is_sender_membership = |event: &PduEvent| {
*event.event_type() == StateEventType::RoomMember.into()
&& event
@@ -845,14 +853,25 @@ async fn load_joined_room(
.is_some_and(is_equal_to!(sender_user.as_str()))
};
let joined_sender_member: Option<_> = (joined_since_last_sync && timeline_pdus.is_empty())
.then(|| {
state_events
.iter()
.position(is_sender_membership)
.map(|pos| state_events.swap_remove(pos))
})
.flatten();
let joined_sender_member: Option<_> =
(joined_since_last_sync && timeline_pdus.is_empty() && !initial)
.then(|| {
state_events
.iter()
.position(is_sender_membership)
.map(|pos| state_events.swap_remove(pos))
})
.flatten();
let prev_batch = timeline_pdus.first().map(at!(0)).or_else(|| {
joined_sender_member
.is_some()
.then_some(since)
.map(Into::into)
});
let send_notification_counts =
last_notification_read.is_none_or(|last_count| last_count.gt(&since));
let notification_count: OptionFuture<_> = send_notification_counts
.then(|| {
@@ -898,19 +917,26 @@ async fn load_joined_room(
})
.unwrap_or(Vec::new());
let keys_changed = services
.users
.room_keys_changed(room_id, since, Some(next_batch))
.map(|(user_id, _)| user_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
let extract_membership = |event: &PduEvent| {
let content: RoomMemberEventContent = event.get_content().ok()?;
let user_id: OwnedUserId = event.state_key()?.parse().ok()?;
Some((content, user_id))
Some((content.membership, user_id))
};
let timeline_membership_changes: Vec<_> = timeline_pdus
let timeline_membership_changes = timeline_pdus
.iter()
.map(ref_at!(1))
.filter(|_| !initial)
.map(ref_at!(1))
.filter_map(extract_membership)
.collect();
.collect::<Vec<_>>();
let device_list_updates = state_events
.iter()
@@ -918,31 +944,27 @@ async fn load_joined_room(
.ready_filter(|_| !initial)
.ready_filter(|state_event| *state_event.event_type() == RoomMember)
.ready_filter_map(extract_membership)
.chain(timeline_membership_changes.into_iter().stream())
.fold_default(async |(mut dlu, mut leu): pair_of!(HashSet<_>), (content, user_id)| {
.chain(timeline_membership_changes.stream())
.fold_default(async |(mut dlu, mut leu): pair_of!(HashSet<_>), (membership, user_id)| {
use MembershipState::*;
let shares_encrypted_room = async |user_id| {
share_encrypted_room(services, sender_user, user_id, Some(room_id)).await
let requires_update = async |user_id| {
!share_encrypted_room(services, sender_user, user_id, Some(room_id)).await
};
match content.membership {
match membership {
| Join if requires_update(&user_id).await => dlu.insert(user_id),
| Leave => leu.insert(user_id),
| Join if joined_since_last_sync || !shares_encrypted_room(&user_id).await =>
dlu.insert(user_id),
| _ => false,
};
(dlu, leu)
})
.then(async |(mut dlu, leu)| {
dlu.extend(keys_changed.await);
(dlu, leu)
});
let prev_batch = timeline_pdus.first().map(at!(0)).or_else(|| {
joined_sender_member
.is_some()
.then_some(since)
.map(Into::into)
});
let include_in_timeline = |event: &PduEvent| {
let filter = &filter.room.timeline;
filter.matches(event)
@@ -957,33 +979,26 @@ async fn load_joined_room(
.ready_filter(include_in_timeline)
.collect::<Vec<_>>();
let device_updates = services
.users
.room_keys_changed(room_id, since, Some(next_batch))
.map(|(user_id, _)| user_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
let account_data_events = services
.account_data
.changes_since(Some(room_id), sender_user, since, Some(next_batch))
.changes_since(Some(room_id), sender_user, since, None)
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect();
let (
(room_events, account_data_events),
(typing_events, private_read_event),
(notification_count, highlight_count),
((mut device_list_updates, left_encrypted_users), device_updates),
(room_events, account_data_events, typing_events, private_read_event),
) = join3(
(device_list_updates, left_encrypted_users),
) = join4(
join(room_events, account_data_events),
join(typing_events, private_read_event),
join(notification_count, highlight_count),
join(device_list_updates, device_updates),
join4(room_events, account_data_events, typing_events, private_read_event),
device_list_updates,
)
.boxed()
.await;
device_list_updates.extend(device_updates);
let is_in_timeline = |event: &PduEvent| {
room_events
.iter()
@@ -1045,8 +1060,9 @@ async fn load_joined_room(
skip_all,
fields(
full = %full_state,
cs = %current_shortstatehash,
ss = ?since_shortstatehash,
hs = ?horizon_shortstatehash,
cs = %current_shortstatehash,
)
)]
#[allow(clippy::too_many_arguments)]
@@ -1055,16 +1071,13 @@ async fn calculate_state_changes<'a>(
sender_user: &UserId,
room_id: &RoomId,
full_state: bool,
encrypted_room: bool,
since_shortstatehash: Option<ShortStateHash>,
horizon_shortstatehash: Option<ShortStateHash>,
current_shortstatehash: ShortStateHash,
joined_since_last_sync: bool,
witness: Option<&'a Witness>,
) -> Result<StateChanges> {
let initial = full_state || since_shortstatehash.is_none() || joined_since_last_sync;
let incremental = !initial && since_shortstatehash != Some(current_shortstatehash);
let incremental = !full_state && !joined_since_last_sync && since_shortstatehash.is_some();
let horizon_shortstatehash = horizon_shortstatehash.unwrap_or(current_shortstatehash);
@@ -1082,36 +1095,33 @@ async fn calculate_state_changes<'a>(
};
let lazy_state_ids: OptionFuture<_> = witness
.filter(|_| !encrypted_room)
.map(|witness| {
StreamExt::into_future(
witness
.iter()
.stream()
.broad_filter_map(|user_id| state_get_shorteventid(user_id)),
)
witness
.iter()
.stream()
.ready_filter(|&user_id| user_id != sender_user)
.broad_filter_map(|user_id| state_get_shorteventid(user_id))
.into_future()
})
.into();
let state_diff_ids: OptionFuture<_> = incremental
.then(|| {
StreamExt::into_future(
services
.state_accessor
.state_added((since_shortstatehash, horizon_shortstatehash))
.boxed(),
)
services
.state_accessor
.state_added((since_shortstatehash, horizon_shortstatehash))
.boxed()
.into_future()
})
.into();
let current_state_ids: OptionFuture<_> = initial
let current_state_ids: OptionFuture<_> = (!incremental)
.then(|| {
StreamExt::into_future(
services
.state_accessor
.state_full_shortids(horizon_shortstatehash)
.expect_ok(),
)
services
.state_accessor
.state_full_shortids(horizon_shortstatehash)
.expect_ok()
.into_future()
})
.into();
@@ -1119,29 +1129,21 @@ async fn calculate_state_changes<'a>(
.stream()
.chain(state_diff_ids.stream())
.broad_filter_map(async |(shortstatekey, shorteventid)| {
if witness.is_none() || encrypted_room {
return Some(shorteventid);
}
lazy_filter(services, sender_user, shortstatekey, shorteventid).await
lazy_filter(services, sender_user, witness, shortstatekey, shorteventid).await
})
.chain(lazy_state_ids.stream())
.broad_filter_map(|shorteventid| {
services
.short
.get_eventid_from_short(shorteventid)
.timeline
.get_pdu_from_shorteventid(shorteventid)
.ok()
})
.broad_filter_map(async |event_id: OwnedEventId| {
services.timeline.get_pdu(&event_id).ok().await
})
.collect::<Vec<_>>()
.boxed()
.await;
let send_member_counts = state_events
.iter()
.any(|event| event.kind == RoomMember);
.any(|event| *event.kind() == RoomMember);
let member_counts: OptionFuture<_> = send_member_counts
.then(|| calculate_counts(services, room_id, sender_user))
@@ -1161,9 +1163,14 @@ async fn calculate_state_changes<'a>(
async fn lazy_filter(
services: &Services,
sender_user: &UserId,
witness: Option<&Witness>,
shortstatekey: ShortStateKey,
shorteventid: ShortEventId,
) -> Option<ShortEventId> {
if witness.is_none() {
return Some(shorteventid);
}
let (event_type, state_key) = services
.short
.get_statekey_from_short(shortstatekey)
+235 -1149
View File
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
use futures::{StreamExt, future::join};
use ruma::{api::client::sync::sync_events::v5::response, events::AnyRawAccountDataEvent};
use tuwunel_core::{
Result, extract_variant,
utils::{IterStream, ReadyExt, stream::BroadbandExt},
};
use tuwunel_service::sync::Room;
use super::{Connection, SyncInfo, Window, extension_rooms_selector};
#[tracing::instrument(name = "account_data", level = "trace", skip_all)]
pub(super) async fn collect(
sync_info: SyncInfo<'_>,
conn: &Connection,
window: &Window,
) -> Result<response::AccountData> {
let SyncInfo { services, sender_user, .. } = sync_info;
let implicit = conn
.extensions
.account_data
.lists
.as_deref()
.map(<[_]>::iter);
let explicit = conn
.extensions
.account_data
.rooms
.as_deref()
.map(<[_]>::iter);
let rooms = extension_rooms_selector(sync_info, conn, window, implicit, explicit)
.stream()
.broad_filter_map(async |room_id| {
let &Room { roomsince, .. } = conn.rooms.get(room_id)?;
let changes: Vec<_> = services
.account_data
.changes_since(Some(room_id), sender_user, roomsince, Some(conn.next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect()
.await;
changes
.is_empty()
.eq(&false)
.then(move || (room_id.to_owned(), changes))
})
.collect();
let global = services
.account_data
.changes_since(None, sender_user, conn.globalsince, Some(conn.next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global))
.collect();
let (global, rooms) = join(global, rooms).await;
Ok(response::AccountData { global, rooms })
}
+218
View File
@@ -0,0 +1,218 @@
use std::collections::HashSet;
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::{OptionFuture, join, join3},
stream::once,
};
use ruma::{
OwnedUserId, RoomId,
api::client::sync::sync_events::{DeviceLists, v5::response},
events::{
StateEventType, TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use tuwunel_core::{
Result, error,
matrix::{Event, pdu::PduCount},
pair_of,
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt, future::OptionStream,
stream::BroadbandExt,
},
};
use tuwunel_service::sync::Connection;
use super::{SyncInfo, share_encrypted_room};
#[tracing::instrument(name = "e2ee", level = "trace", skip_all)]
pub(super) async fn collect(
sync_info: SyncInfo<'_>,
conn: &Connection,
) -> Result<response::E2EE> {
let SyncInfo { services, sender_user, sender_device, .. } = sync_info;
let keys_changed = services
.users
.keys_changed(sender_user, conn.globalsince, Some(conn.next_batch))
.map(ToOwned::to_owned)
.collect::<HashSet<_>>()
.map(|changed| (changed, HashSet::new()));
let (changed, left) = (HashSet::new(), HashSet::new());
let (changed, left) = services
.state_cache
.rooms_joined(sender_user)
.map(ToOwned::to_owned)
.broad_filter_map(async |room_id| collect_room(sync_info, conn, &room_id).await.ok())
.chain(once(keys_changed))
.ready_fold((changed, left), |(mut changed, mut left), room| {
changed.extend(room.0);
left.extend(room.1);
(changed, left)
})
.await;
let left = left
.into_iter()
.stream()
.filter_map(async |user_id| {
share_encrypted_room(services, sender_user, &user_id, None)
.await
.is_false()
.then_some(user_id)
})
.collect();
let device_one_time_keys_count = services
.users
.last_one_time_keys_update(sender_user)
.then(|since| -> OptionFuture<_> {
since
.gt(&conn.globalsince)
.then(|| {
services
.users
.count_one_time_keys(sender_user, sender_device)
})
.into()
})
.map(Option::unwrap_or_default);
let (left, device_one_time_keys_count) = join(left, device_one_time_keys_count)
.boxed()
.await;
Ok(response::E2EE {
device_one_time_keys_count,
device_unused_fallback_key_types: None,
device_lists: DeviceLists {
changed: changed.into_iter().collect(),
left,
},
})
}
#[tracing::instrument(level = "trace", skip_all, fields(room_id), ret)]
async fn collect_room(
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
conn: &Connection,
room_id: &RoomId,
) -> Result<pair_of!(HashSet<OwnedUserId>)> {
let current_shortstatehash = services
.state
.get_room_shortstatehash(room_id)
.inspect_err(|e| error!("Room {room_id} has no state: {e}"));
let room_keys_changed = services
.users
.room_keys_changed(room_id, conn.globalsince, Some(conn.next_batch))
.map(|(user_id, _)| user_id)
.map(ToOwned::to_owned)
.collect::<HashSet<_>>();
let (current_shortstatehash, device_list_changed) =
join(current_shortstatehash, room_keys_changed)
.boxed()
.await;
let lists = (device_list_changed, HashSet::new());
let Ok(current_shortstatehash) = current_shortstatehash else {
return Ok(lists);
};
if current_shortstatehash <= conn.globalsince {
return Ok(lists);
}
let Ok(since_shortstatehash) = services
.timeline
.prev_shortstatehash(room_id, PduCount::Normal(conn.globalsince).saturating_add(1))
.await
else {
return Ok(lists);
};
if since_shortstatehash == current_shortstatehash {
return Ok(lists);
}
let encrypted_room = services
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
.is_ok();
let since_encryption = services
.state_accessor
.state_get(since_shortstatehash, &StateEventType::RoomEncryption, "")
.is_ok();
let sender_joined_count = services
.state_cache
.get_joined_count(room_id, sender_user);
let (encrypted_room, since_encryption, sender_joined_count) =
join3(encrypted_room, since_encryption, sender_joined_count).await;
if !encrypted_room {
return Ok(lists);
}
let encrypted_since_last_sync = !since_encryption;
let joined_since_last_sync = sender_joined_count.is_ok_and(|count| count > conn.globalsince);
let joined_members_burst: OptionFuture<_> = (joined_since_last_sync
|| encrypted_since_last_sync)
.then(|| {
services
.state_cache
.room_members(room_id)
.ready_filter(|&user_id| user_id != sender_user)
.map(ToOwned::to_owned)
.map(|user_id| (MembershipState::Join, user_id))
.into_future()
})
.into();
services
.state_accessor
.state_added((since_shortstatehash, current_shortstatehash))
.broad_filter_map(async |(_shortstatekey, shorteventid)| {
services
.timeline
.get_pdu_from_shorteventid(shorteventid)
.ok()
.await
})
.ready_filter(|event| *event.kind() == TimelineEventType::RoomMember)
.ready_filter(|event| {
event
.state_key()
.is_some_and(|state_key| state_key != sender_user)
})
.ready_filter_map(|event| {
let content: RoomMemberEventContent = event.get_content().ok()?;
let user_id: OwnedUserId = event.state_key()?.parse().ok()?;
Some((content.membership, user_id))
})
.chain(joined_members_burst.stream())
.fold(lists, async |(mut changed, mut left), (membership, user_id)| {
use MembershipState::*;
let should_add = async |user_id| {
!share_encrypted_room(services, sender_user, user_id, Some(room_id)).await
};
match membership {
| Join if should_add(&user_id).await => changed.insert(user_id),
| Leave => left.insert(user_id),
| _ => false,
};
(changed, left)
})
.map(Ok)
.boxed()
.await
}
+153
View File
@@ -0,0 +1,153 @@
use futures::{StreamExt, future::OptionFuture, pin_mut};
use ruma::{
RoomId, api::client::sync::sync_events::v5::request::ListFilters, directory::RoomTypeFilter,
events::room::member::MembershipState,
};
use tuwunel_core::{
is_equal_to, is_true,
utils::{
BoolExt, FutureBoolExt, IterStream, ReadyExt,
future::{self, OptionExt, ReadyEqExt},
},
};
use super::SyncInfo;
#[tracing::instrument(name = "filter", level = "trace", skip_all)]
pub(super) async fn filter_room(
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
filter: &ListFilters,
room_id: &RoomId,
membership: Option<&MembershipState>,
) -> bool {
let match_invite: OptionFuture<_> = filter
.is_invite
.map(async |is_invite| match (membership, is_invite) {
| (Some(MembershipState::Invite), true) => true,
| (Some(MembershipState::Invite), false) => false,
| (Some(_), true) => false,
| (Some(_), false) => true,
| _ =>
services
.state_cache
.is_invited(sender_user, room_id)
.await == is_invite,
})
.into();
let match_direct: OptionFuture<_> = filter
.is_dm
.map(async |is_dm| {
services
.account_data
.is_direct(sender_user, room_id)
.await == is_dm
})
.into();
let match_direct_member: OptionFuture<_> = filter
.is_dm
.map(async |is_dm| {
services
.state_accessor
.is_direct(room_id, sender_user)
.await == is_dm
})
.into();
let match_encrypted: OptionFuture<_> = filter
.is_encrypted
.map(async |is_encrypted| {
services
.state_accessor
.is_encrypted_room(room_id)
.await == is_encrypted
})
.into();
let match_space_child: OptionFuture<_> = filter
.spaces
.is_empty()
.is_false()
.then(async || {
filter
.spaces
.iter()
.stream()
.flat_map(|room_id| services.spaces.get_space_children(room_id))
.ready_any(is_equal_to!(room_id))
.await
})
.into();
let fetch_tags = !filter.tags.is_empty() || !filter.not_tags.is_empty();
let match_room_tag: OptionFuture<_> = fetch_tags
.then(async || {
if let Some(tags) = services
.account_data
.get_room_tags(sender_user, room_id)
.await
.ok()
.filter(|tags| !tags.is_empty())
{
tags.keys().any(|tag| {
(filter.not_tags.is_empty() || !filter.not_tags.contains(tag))
|| (!filter.tags.is_empty() && filter.tags.contains(tag))
})
} else {
filter.tags.is_empty()
}
})
.into();
let fetch_room_type = !filter.room_types.is_empty() || !filter.not_room_types.is_empty();
let match_room_type: OptionFuture<_> = fetch_room_type
.then(async || {
let room_type = services
.state_accessor
.get_room_type(room_id)
.await
.ok();
let room_type = RoomTypeFilter::from(room_type);
(filter.not_room_types.is_empty() || !filter.not_room_types.contains(&room_type))
&& (filter.room_types.is_empty() || filter.room_types.contains(&room_type))
})
.into();
future::and7(
match_invite.is_none_or(is_true!()),
match_encrypted.is_none_or(is_true!()),
match_direct.is_none_or(is_true!()),
match_direct_member.is_none_or(is_true!()),
match_space_child.is_none_or(is_true!()),
match_room_type.is_none_or(is_true!()),
match_room_tag.is_none_or(is_true!()),
)
.await
}
#[tracing::instrument(name = "filter_meta", level = "trace", skip_all)]
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 is_disabled = services.metadata.is_disabled(room_id);
let is_banned = services.metadata.is_banned(room_id);
let not_visible = services
.state_accessor
.user_can_see_state_events(sender_user, room_id)
.eq(&false);
pin_mut!(not_visible, not_exists, is_disabled, is_banned);
not_visible
.or(not_exists)
.or(is_disabled)
.or(is_banned)
.await
.eq(&false)
}
+93
View File
@@ -0,0 +1,93 @@
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedRoomId, RoomId,
api::client::sync::sync_events::v5::response,
events::{AnySyncEphemeralRoomEvent, receipt::SyncReceiptEvent},
serde::Raw,
};
use tuwunel_core::{
Result,
utils::{BoolExt, IterStream, stream::BroadbandExt},
};
use tuwunel_service::{rooms::read_receipt::pack_receipts, sync::Room};
use super::{Connection, SyncInfo, Window, extension_rooms_selector};
#[tracing::instrument(name = "receipts", level = "trace", skip_all)]
pub(super) async fn collect(
sync_info: SyncInfo<'_>,
conn: &Connection,
window: &Window,
) -> Result<response::Receipts> {
let SyncInfo { .. } = sync_info;
let implicit = conn
.extensions
.receipts
.lists
.as_deref()
.map(<[_]>::iter);
let explicit = conn
.extensions
.receipts
.rooms
.as_deref()
.map(<[_]>::iter);
let rooms = extension_rooms_selector(sync_info, conn, window, implicit, explicit)
.stream()
.broad_filter_map(|room_id| collect_room(sync_info, conn, window, room_id))
.collect()
.await;
Ok(response::Receipts { rooms })
}
#[tracing::instrument(level = "trace", skip_all, fields(room_id), ret)]
async fn collect_room(
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
conn: &Connection,
_window: &Window,
room_id: &RoomId,
) -> Option<(OwnedRoomId, Raw<SyncReceiptEvent>)> {
let &Room { roomsince, .. } = conn.rooms.get(room_id)?;
let private_receipt = services
.read_receipt
.last_privateread_update(sender_user, room_id)
.then(async |last_private_update| {
if last_private_update <= roomsince || last_private_update > conn.next_batch {
return None;
}
services
.read_receipt
.private_read_get(room_id, sender_user)
.map(Some)
.await
})
.map(Option::into_iter)
.map(Iterator::flatten)
.map(IterStream::stream)
.flatten_stream();
let receipts: Vec<Raw<AnySyncEphemeralRoomEvent>> = services
.read_receipt
.readreceipts_since(room_id, roomsince, Some(conn.next_batch))
.filter_map(async |(read_user, _ts, v)| {
services
.users
.user_is_ignored(read_user, sender_user)
.await
.or_some(v)
})
.chain(private_receipt)
.collect()
.boxed()
.await;
receipts
.is_empty()
.is_false()
.then(|| (room_id.to_owned(), pack_receipts(receipts.into_iter())))
}
+415
View File
@@ -0,0 +1,415 @@
use std::{cmp::Ordering, collections::HashSet};
use futures::{
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
future::{OptionFuture, join, join3, join4},
};
use ruma::{
JsOption, MxcUri, OwnedMxcUri, RoomId, UserId,
api::client::sync::sync_events::{
UnreadNotificationsCount,
v5::{DisplayName, response, response::Heroes},
},
events::{
StateEventType,
TimelineEventType::{
self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
},
room::member::MembershipState,
},
};
use tuwunel_core::{
Result, at, err, is_equal_to,
matrix::{Event, StateKey, pdu::PduCount},
ref_at,
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt, math::usize_from_ruma, result::FlatOk,
stream::BroadbandExt,
},
};
use tuwunel_service::{Services, sync::Room};
use super::{super::load_timeline, Connection, SyncInfo, WindowRoom};
use crate::client::ignored_filter;
static DEFAULT_BUMP_TYPES: [TimelineEventType; 6] =
[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
#[tracing::instrument(
name = "room",
level = "debug",
skip_all,
fields(room_id, roomsince)
)]
#[allow(clippy::too_many_arguments)]
pub(super) async fn handle(
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
conn: &Connection,
WindowRoom {
lists, membership, room_id, last_count, ..
}: &WindowRoom,
) -> Result<Option<response::Room>> {
debug_assert!(
DEFAULT_BUMP_TYPES.is_sorted(),
"DEFAULT_BUMP_TYPES must be sorted for binary search"
);
let &Room { roomsince, .. } = conn
.rooms
.get(room_id)
.ok_or_else(|| err!("Missing connection state for {room_id}"))?;
debug_assert!(
*last_count > roomsince || *last_count == 0 || roomsince == 0,
"Stale room shouldn't be in the window"
);
if *membership == Some(MembershipState::Leave) {
return Ok(Some(response::Room {
initial: roomsince.eq(&0).then_some(true),
lists: lists.clone(),
membership: membership.clone(),
prev_batch: Some(conn.next_batch.to_string().into()),
limited: true,
required_state: vec![
services
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, sender_user.as_str())
.map_ok(Event::into_format)
.await?,
],
..Default::default()
}));
}
let is_invite = *membership == Some(MembershipState::Invite);
let default_details = (0_usize, HashSet::new());
let (timeline_limit, required_state) = lists
.iter()
.filter_map(|list_id| conn.lists.get(list_id))
.map(|list| &list.room_details)
.chain(conn.subscriptions.get(room_id).into_iter())
.fold(default_details, |(mut timeline_limit, mut required_state), config| {
let limit = usize_from_ruma(config.timeline_limit);
timeline_limit = timeline_limit.max(limit);
required_state.extend(config.required_state.clone());
(timeline_limit, required_state)
});
let timeline: OptionFuture<_> = is_invite
.is_false()
.then(|| {
load_timeline(
services,
sender_user,
room_id,
PduCount::Normal(roomsince),
Some(PduCount::from(conn.next_batch)),
timeline_limit,
)
})
.into();
let (timeline_pdus, limited, _lastcount) = timeline
.await
.flat_ok()
.unwrap_or_else(|| (Vec::new(), true, PduCount::default()));
let required_state = required_state
.into_iter()
.filter(|_| !timeline_pdus.is_empty())
.collect::<Vec<_>>();
let prev_batch = timeline_pdus
.first()
.map(at!(0))
.map(PduCount::into_unsigned)
.as_ref()
.map(ToString::to_string);
let bump_stamp = timeline_pdus
.iter()
.filter(|(_, pdu)| {
if *pdu.event_type() == TimelineEventType::RoomMember {
return pdu
.state_key()
.is_some_and(is_equal_to!(sender_user.as_str()));
}
DEFAULT_BUMP_TYPES
.binary_search(pdu.event_type())
.is_ok()
})
.map(at!(0))
.filter(|count| matches!(count, PduCount::Normal(_)))
.map(PduCount::into_unsigned)
.max()
.map(TryInto::try_into)
.flat_ok();
let num_live: OptionFuture<_> = roomsince
.ne(&0)
.and_is(limited || timeline_pdus.len() >= timeline_limit)
.then(|| {
services
.timeline
.pdus(None, room_id, Some(roomsince.into()))
.count()
.map(TryInto::try_into)
.map(Result::ok)
})
.into();
let lazy = required_state
.iter()
.any(is_equal_to!(&(StateEventType::RoomMember, "$LAZY".into())));
let mut timeline_senders: Vec<_> = timeline_pdus
.iter()
.filter(|_| lazy)
.map(ref_at!(1))
.map(Event::sender)
.collect();
timeline_senders.sort();
timeline_senders.dedup();
let timeline_senders = timeline_senders
.iter()
.map(|sender| (StateEventType::RoomMember, StateKey::from_str(sender.as_str())))
.stream();
let wildcard_state = required_state
.iter()
.filter(|(_, state_key)| state_key == "*")
.map(|(event_type, _)| {
services
.state_accessor
.room_state_keys(room_id, event_type)
.map_ok(|state_key| (event_type.clone(), state_key))
.ready_filter_map(Result::ok)
})
.stream()
.flatten();
let required_state = required_state
.iter()
.cloned()
.stream()
.chain(wildcard_state)
.chain(timeline_senders)
.broad_filter_map(async |state| {
let state_key: StateKey = match state.1.as_str() {
| "$LAZY" | "*" => return None,
| "$ME" => sender_user.as_str().into(),
| _ => state.1.clone(),
};
services
.state_accessor
.room_state_get(room_id, &state.0, &state_key)
.map_ok(Event::into_format)
.ok()
.await
})
.collect();
// TODO: figure out a timestamp we can use for remote invites
let invite_state: OptionFuture<_> = is_invite
.then(|| {
services
.state_cache
.invite_state(sender_user, room_id)
.ok()
})
.into();
let room_name = services
.state_accessor
.get_name(room_id)
.map_ok(Into::into)
.map(Result::ok);
let room_avatar = services
.state_accessor
.get_avatar(room_id)
.map_ok(|content| content.url)
.ok()
.map(Option::flatten);
let highlight_count = services
.user
.highlight_count(sender_user, room_id)
.map(TryInto::try_into)
.map(Result::ok);
let notification_count = services
.user
.notification_count(sender_user, room_id)
.map(TryInto::try_into)
.map(Result::ok);
let joined_count = services
.state_cache
.room_joined_count(room_id)
.map_ok(TryInto::try_into)
.map_ok(Result::ok)
.map(FlatOk::flat_ok);
let invited_count = services
.state_cache
.room_invited_count(room_id)
.map_ok(TryInto::try_into)
.map_ok(Result::ok)
.map(FlatOk::flat_ok);
let is_dm = services
.state_accessor
.is_direct(room_id, sender_user)
.map(|is_dm| is_dm.then_some(is_dm));
let last_read_count = services
.user
.last_notification_read(sender_user, room_id);
let timeline = timeline_pdus
.iter()
.stream()
.filter_map(|item| ignored_filter(services, item.clone(), sender_user))
.map(at!(1))
.map(Event::into_format)
.collect();
let meta = join3(room_name, room_avatar, is_dm);
let events = join4(timeline, num_live, required_state, invite_state);
let member_counts = join(joined_count, invited_count);
let notification_counts = join3(highlight_count, notification_count, last_read_count);
let (
(room_name, room_avatar, is_dm),
(timeline, num_live, required_state, invite_state),
(joined_count, invited_count),
(highlight_count, notification_count, _last_notification_read),
) = join4(meta, events, member_counts, notification_counts)
.boxed()
.await;
let (heroes, hero_name, heroes_avatar) = calculate_heroes(
services,
sender_user,
room_id,
room_name.as_ref(),
room_avatar.as_deref(),
)
.await?;
Ok(Some(response::Room {
initial: roomsince.eq(&0).then_some(true),
lists: lists.clone(),
membership: membership.clone(),
name: room_name.or(hero_name),
avatar: JsOption::from_option(room_avatar.or(heroes_avatar)),
is_dm,
heroes,
required_state,
invite_state: invite_state.flatten(),
prev_batch: prev_batch.as_deref().map(Into::into),
num_live: num_live.flatten(),
limited,
timeline,
bump_stamp,
joined_count,
invited_count,
unread_notifications: UnreadNotificationsCount { highlight_count, notification_count },
}))
}
#[tracing::instrument(name = "heroes", level = "trace", skip_all)]
#[allow(clippy::type_complexity)]
async fn calculate_heroes(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
room_name: Option<&DisplayName>,
room_avatar: Option<&MxcUri>,
) -> Result<(Option<Heroes>, Option<DisplayName>, Option<OwnedMxcUri>)> {
const MAX_HEROES: usize = 5;
let heroes: Heroes = services
.state_cache
.room_members(room_id)
.ready_filter(|&member| member != sender_user)
.ready_filter_map(|member| room_name.is_none().then_some(member))
.map(ToOwned::to_owned)
.broadn_filter_map(MAX_HEROES, async |user_id| {
let content = services
.state_accessor
.get_member(room_id, &user_id)
.await
.ok()?;
let name: OptionFuture<_> = content
.displayname
.is_none()
.then(|| services.users.displayname(&user_id).ok())
.into();
let avatar: OptionFuture<_> = content
.avatar_url
.is_none()
.then(|| services.users.avatar_url(&user_id).ok())
.into();
let (name, avatar) = join(name, avatar).await;
let hero = response::Hero {
user_id,
avatar: avatar.unwrap_or(content.avatar_url),
name: name
.unwrap_or(content.displayname)
.map(Into::into),
};
Some(hero)
})
.take(MAX_HEROES)
.collect()
.await;
let hero_name = match heroes.len().cmp(&(1_usize)) {
| Ordering::Less => None,
| Ordering::Equal => Some(
heroes[0]
.name
.clone()
.unwrap_or_else(|| heroes[0].user_id.as_str().into()),
),
| Ordering::Greater => {
let firsts = heroes[1..]
.iter()
.map(|h| {
h.name
.clone()
.unwrap_or_else(|| h.user_id.as_str().into())
})
.collect::<Vec<_>>()
.join(", ");
let last = heroes[0]
.name
.clone()
.unwrap_or_else(|| heroes[0].user_id.as_str().into());
Some(format!("{firsts} and {last}")).map(Into::into)
},
};
let heroes_avatar = (room_avatar.is_none() && room_name.is_none())
.then(|| {
heroes
.first()
.and_then(|hero| hero.avatar.clone())
})
.flatten();
Ok((Some(heroes), hero_name, heroes_avatar))
}
+266
View File
@@ -0,0 +1,266 @@
use std::cmp::Ordering;
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::{OptionFuture, join5},
};
use ruma::{OwnedRoomId, UInt, events::room::member::MembershipState, uint};
use tuwunel_core::{
apply, is_true,
matrix::PduCount,
trace,
utils::{
BoolExt,
future::TryExtExt,
math::usize_from_ruma,
stream::{BroadbandExt, IterStream},
},
};
use tuwunel_service::sync::Connection;
use super::{
ListIds, ResponseLists, SyncInfo, Window, WindowRoom, filter_room, filter_room_meta,
};
#[tracing::instrument(level = "debug", skip_all)]
pub(super) async fn selector(
conn: &mut Connection,
sync_info: SyncInfo<'_>,
) -> (Window, ResponseLists) {
use MembershipState::*;
let SyncInfo { services, sender_user, .. } = sync_info;
let mut rooms = services
.state_cache
.user_memberships(sender_user, Some(&[Join, Invite, Knock]))
.map(|(membership, room_id)| (room_id.to_owned(), Some(membership)))
.broad_filter_map(|(room_id, membership)| {
match_lists_for_room(sync_info, conn, room_id, membership)
})
.collect::<Vec<_>>()
.await;
rooms.sort_by(room_sort);
rooms
.iter_mut()
.enumerate()
.for_each(|(i, room)| {
room.ranked = i;
conn.rooms
.entry(room.room_id.clone())
.or_default();
});
trace!(?rooms);
let lists = response_lists(rooms.iter());
trace!(?lists);
let window = select_window(sync_info, conn, rooms.iter(), &lists).await;
trace!(?window);
(window, lists)
}
#[tracing::instrument(
name = "window",
level = "debug",
skip_all,
fields(rooms = rooms.clone().count())
)]
async fn select_window<'a, Rooms>(
sync_info: SyncInfo<'_>,
conn: &Connection,
rooms: Rooms,
lists: &ResponseLists,
) -> Window
where
Rooms: Iterator<Item = &'a WindowRoom> + Clone + Send + Sync,
{
static FULL_RANGE: (UInt, UInt) = (UInt::MIN, UInt::MAX);
let SyncInfo { services, sender_user, .. } = sync_info;
let selections = lists
.keys()
.cloned()
.filter_map(|id| conn.lists.get(&id).map(|list| (id, list)))
.flat_map(|(id, list)| {
let full_range = list
.ranges
.is_empty()
.then_some(&FULL_RANGE)
.into_iter();
list.ranges
.iter()
.chain(full_range)
.map(apply!(2, usize_from_ruma))
.map(move |range| (id.clone(), range))
})
.flat_map(|(id, (start, end))| {
rooms
.clone()
.filter(move |&room| room.lists.contains(&id))
.filter(|&room| {
conn.rooms
.get(&room.room_id)
.is_some_and(|conn_room| {
conn_room.roomsince == 0 || room.last_count > conn_room.roomsince
})
})
.enumerate()
.skip_while(move |&(i, _)| i < start)
.take(end.saturating_add(1).saturating_sub(start))
.map(|(_, room)| (room.room_id.clone(), room.clone()))
})
.stream();
let subscriptions = conn
.subscriptions
.iter()
.stream()
.broad_filter_map(async |(room_id, _)| {
filter_room_meta(sync_info, room_id)
.await
.into_option()?;
Some(WindowRoom {
room_id: room_id.clone(),
lists: Default::default(),
ranked: usize::MAX,
last_count: 0,
membership: services
.state_cache
.user_membership(sender_user, room_id)
.await,
})
})
.map(|room| (room.room_id.clone(), room));
subscriptions.chain(selections).collect().await
}
#[tracing::instrument(
name = "matcher",
level = "trace",
skip_all,
fields(?room_id, ?membership)
)]
async fn match_lists_for_room(
sync_info: SyncInfo<'_>,
conn: &Connection,
room_id: OwnedRoomId,
membership: Option<MembershipState>,
) -> Option<WindowRoom> {
let SyncInfo { services, sender_user, .. } = sync_info;
let (matched, lists) = conn
.lists
.iter()
.stream()
.filter_map(async |(id, list)| {
let filter: OptionFuture<_> = list
.filters
.clone()
.map(async |filters| {
filter_room(sync_info, &filters, &room_id, membership.as_ref()).await
})
.into();
filter
.await
.is_none_or(is_true!())
.then(|| id.clone())
})
.collect::<ListIds>()
.map(|lists| (lists.is_empty().is_false(), lists))
.await;
let last_notification: OptionFuture<_> = matched
.then(|| {
services
.user
.last_notification_read(sender_user, &room_id)
})
.into();
let last_privateread: OptionFuture<_> = matched
.then(|| {
services
.read_receipt
.last_privateread_update(sender_user, &room_id)
})
.into();
let last_receipt: OptionFuture<_> = matched
.then(|| {
services
.read_receipt
.last_receipt_count(&room_id, sender_user.into(), None)
.unwrap_or_default()
})
.into();
let last_account: OptionFuture<_> = matched
.then(|| {
services
.account_data
.last_count(Some(room_id.as_ref()), sender_user, Some(conn.next_batch))
.unwrap_or_default()
})
.into();
let last_timeline: OptionFuture<_> = matched
.then(|| {
services
.timeline
.last_timeline_count(None, &room_id, Some(conn.next_batch.into()))
.map_ok(PduCount::into_unsigned)
.unwrap_or_default()
})
.into();
let (last_timeline, last_notification, last_account, last_receipt, last_privateread) =
join5(last_timeline, last_notification, last_account, last_receipt, last_privateread)
.await;
Some(WindowRoom {
room_id: room_id.clone(),
membership,
lists,
ranked: 0,
last_count: [
last_timeline,
last_notification,
last_account,
last_receipt,
last_privateread,
]
.into_iter()
.map(Option::unwrap_or_default)
.filter(|count| conn.next_batch.ge(count))
.max()
.unwrap_or_default(),
})
}
fn response_lists<'a, Rooms>(rooms: Rooms) -> ResponseLists
where
Rooms: Iterator<Item = &'a WindowRoom>,
{
rooms
.flat_map(|room| room.lists.iter())
.fold(ResponseLists::default(), |mut lists, id| {
let list = lists.entry(id.clone()).or_default();
list.count = list
.count
.checked_add(uint!(1))
.expect("list count must not overflow JsInt");
lists
})
}
fn room_sort(a: &WindowRoom, b: &WindowRoom) -> Ordering { b.last_count.cmp(&a.last_count) }
+32
View File
@@ -0,0 +1,32 @@
use futures::StreamExt;
use ruma::api::client::sync::sync_events::v5::response;
use tuwunel_core::{self, Result};
use super::{Connection, SyncInfo};
#[tracing::instrument(name = "to_device", level = "trace", skip_all, ret)]
pub(super) async fn collect(
SyncInfo { services, sender_user, sender_device, .. }: SyncInfo<'_>,
conn: &Connection,
) -> Result<Option<response::ToDevice>> {
services
.users
.remove_to_device_events(sender_user, sender_device, conn.globalsince)
.await;
let events: Vec<_> = services
.users
.get_to_device_events(sender_user, sender_device, None, Some(conn.next_batch))
.collect()
.await;
let to_device = events
.is_empty()
.eq(&false)
.then(|| response::ToDevice {
next_batch: conn.next_batch.to_string().into(),
events,
});
Ok(to_device)
}
+63
View File
@@ -0,0 +1,63 @@
use std::collections::BTreeMap;
use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::{
api::client::sync::sync_events::v5::response,
events::typing::{SyncTypingEvent, TypingEventContent},
serde::Raw,
};
use tuwunel_core::{
Result, debug_error,
utils::{IterStream, ReadyExt},
};
use super::{Connection, SyncInfo, Window, extension_rooms_selector};
#[tracing::instrument(name = "typing", level = "trace", skip_all, ret)]
pub(super) async fn collect(
sync_info: SyncInfo<'_>,
conn: &Connection,
window: &Window,
) -> Result<response::Typing> {
use response::Typing;
let SyncInfo { services, sender_user, .. } = sync_info;
let implicit = conn
.extensions
.typing
.lists
.as_deref()
.map(<[_]>::iter);
let explicit = conn
.extensions
.typing
.rooms
.as_deref()
.map(<[_]>::iter);
extension_rooms_selector(sync_info, conn, window, implicit, explicit)
.stream()
.filter_map(async |room_id| {
services
.typing
.typing_users_for_user(room_id, sender_user)
.inspect_err(|e| debug_error!(%room_id, "Failed to get typing events: {e}"))
.await
.ok()
.filter(|users| !users.is_empty())
.map(|users| (room_id, users))
})
.ready_filter_map(|(room_id, users)| {
let content = TypingEventContent::new(users);
let event = SyncTypingEvent { content };
let event = Raw::new(&event);
Some((room_id.to_owned(), event.ok()?))
})
.collect::<BTreeMap<_, _>>()
.map(|rooms| Typing { rooms })
.map(Ok)
.await
}
+5 -7
View File
@@ -28,7 +28,7 @@ pub(crate) async fn create_typing_event_route(
match body.state {
| Typing::Yes(duration) => {
let duration = utils::clamp(
let duration = Ord::clamp(
duration
.as_millis()
.try_into()
@@ -64,12 +64,10 @@ pub(crate) async fn create_typing_event_route(
}
// ping presence
if services.config.allow_local_presence {
services
.presence
.ping_presence(&body.user_id, &ruma::presence::PresenceState::Online)
.await?;
}
services
.presence
.maybe_ping_presence(&body.user_id, &ruma::presence::PresenceState::Online)
.await?;
Ok(create_typing_event::v3::Response {})
}
+20 -28
View File
@@ -73,13 +73,11 @@ pub(crate) async fn delete_timezone_key_route(
services.users.set_timezone(&body.user_id, None);
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await?;
Ok(delete_timezone_key::unstable::Response {})
}
@@ -103,13 +101,11 @@ pub(crate) async fn set_timezone_key_route(
.users
.set_timezone(&body.user_id, body.tz.clone());
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await?;
Ok(set_timezone_key::unstable::Response {})
}
@@ -171,13 +167,11 @@ pub(crate) async fn set_profile_field_route(
);
}
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await?;
Ok(set_profile_field::v3::Response {})
}
@@ -227,13 +221,11 @@ pub(crate) async fn delete_profile_field_route(
.set_profile_key(&body.user_id, body.field.as_str(), None);
}
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
// Presence update
services
.presence
.maybe_ping_presence(&body.user_id, &PresenceState::Online)
.await?;
Ok(delete_profile_field::v3::Response {})
}
+5 -8
View File
@@ -25,12 +25,12 @@ pub(crate) async fn turn_server_route(
return Err!(Request(NotFound("Not Found")));
}
let turn_secret = services.globals.turn_secret.clone();
let turn_secret = &services.config.turn_secret;
let (username, password) = if !turn_secret.is_empty() {
let expiry = SecondsSinceUnixEpoch::from_system_time(
SystemTime::now()
.checked_add(Duration::from_secs(services.globals.turn_ttl()))
.checked_add(Duration::from_secs(services.config.turn_ttl))
.expect("TURN TTL should not get this high"),
)
.expect("time is valid");
@@ -53,16 +53,13 @@ pub(crate) async fn turn_server_route(
(username, password)
} else {
(
services.globals.turn_username().clone(),
services.globals.turn_password().clone(),
)
(services.config.turn_username.clone(), services.config.turn_password.clone())
};
Ok(get_turn_server_info::v3::Response {
username,
password,
uris: services.globals.turn_uris().to_vec(),
ttl: Duration::from_secs(services.globals.turn_ttl()),
uris: services.config.turn_uris.clone(),
ttl: Duration::from_secs(services.config.turn_ttl),
})
}
+1 -1
View File
@@ -1,4 +1,4 @@
#![type_length_limit = "163840"] //TODO: REDUCE ME
#![type_length_limit = "262144"] //TODO: REDUCE ME
#![allow(clippy::toplevel_ref_arg)]
pub mod client;
+7 -1
View File
@@ -53,6 +53,12 @@ pub(crate) async fn get_backfill_route(
.ready_fold(PduCount::min(), cmp::max)
.await;
let room_version = services
.state
.get_room_version(&body.room_id)
.await
.ok();
Ok(get_backfill::v1::Response {
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
@@ -79,7 +85,7 @@ pub(crate) async fn get_backfill_route(
.and_then(|pdu| {
services
.federation
.format_pdu_into(pdu, None)
.format_pdu_into(pdu, room_version.as_ref())
.map(Ok)
})
.try_collect()
+14 -11
View File
@@ -1,5 +1,6 @@
use axum::extract::State;
use ruma::{MilliSecondsSinceUnixEpoch, RoomId, api::federation::event::get_event};
use futures::{FutureExt, future::try_join};
use ruma::{MilliSecondsSinceUnixEpoch, OwnedRoomId, api::federation::event::get_event};
use tuwunel_core::{Result, err};
use super::AccessCheck;
@@ -21,28 +22,30 @@ pub(crate) async fn get_event_route(
.await
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
let room_id: &RoomId = event
let room_id: OwnedRoomId = event
.get("room_id")
.and_then(|val| val.as_str())
.ok_or_else(|| err!(Database("Invalid event in database.")))?
.try_into()
.map_err(|_| err!(Database("Invalid room_id in event in database.")))?;
AccessCheck {
let access_check = AccessCheck {
services: &services,
origin: body.origin(),
room_id,
room_id: &room_id,
event_id: Some(&body.event_id),
}
.check()
.await?;
};
let pdu = services
.federation
.format_pdu_into(event, None)
.map(Ok);
let ((), pdu) = try_join(access_check.check(), pdu).await?;
Ok(get_event::v1::Response {
origin: services.globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
pdu: services
.federation
.format_pdu_into(event, None)
.await,
pdu,
})
}
+20 -3
View File
@@ -6,7 +6,10 @@
RoomId,
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
};
use tuwunel_core::{Error, Result, utils::stream::ReadyExt};
use tuwunel_core::{
Error, Result,
utils::stream::{BroadbandExt, ReadyExt},
};
use super::AccessCheck;
use crate::Ruma;
@@ -43,12 +46,26 @@ pub(crate) async fn get_event_authorization_route(
let room_id = <&RoomId>::try_from(room_id_str)
.map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
let room_version = services
.state
.get_room_version(room_id)
.await
.ok();
let auth_chain = services
.auth_chain
.event_ids_iter(room_id, once(body.event_id.borrow()))
.ready_filter_map(Result::ok)
.filter_map(async |id| services.timeline.get_pdu_json(&id).await.ok())
.then(|pdu| services.federation.format_pdu_into(pdu, None))
.broad_filter_map(async |id| {
let pdu = services.timeline.get_pdu_json(&id).await.ok()?;
let pdu = services
.federation
.format_pdu_into(pdu, room_version.as_ref())
.await;
Some(pdu)
})
.collect()
.await;
+7 -1
View File
@@ -32,6 +32,12 @@ pub(crate) async fn get_missing_events_route(
.unwrap_or(LIMIT_DEFAULT)
.min(LIMIT_MAX);
let room_version = services
.state
.get_room_version(&body.room_id)
.await
.ok();
let mut queued_events = body.latest_events.clone();
// the vec will never have more entries the limit
let mut events = Vec::with_capacity(limit);
@@ -78,7 +84,7 @@ pub(crate) async fn get_missing_events_route(
let event = services
.federation
.format_pdu_into(event, None)
.format_pdu_into(event, room_version.as_ref())
.await;
queued_events.extend(prev_events);
+4 -1
View File
@@ -12,7 +12,7 @@
};
use tuwunel_core::{
Err, Error, Result, err, extract_variant,
matrix::{Event, PduEvent, event::gen_event_id},
matrix::{Event, PduCount, PduEvent, event::gen_event_id},
utils,
utils::hash::sha256,
warn,
@@ -143,6 +143,7 @@ pub(crate) async fn create_invite_route(
.server_in_room(services.globals.server_name(), &body.room_id)
.await
{
let count = services.globals.next_count();
services
.state_cache
.update_membership(
@@ -153,8 +154,10 @@ pub(crate) async fn create_invite_route(
Some(invite_state),
body.via.clone(),
true,
PduCount::Normal(*count),
)
.await?;
drop(count);
for appservice in services.appservice.read().await.values() {
if appservice.is_user_match(&invited_user) {
+2 -2
View File
@@ -59,8 +59,8 @@ pub(crate) async fn get_public_rooms_route(
body: Ruma<get_public_rooms::v1::Request>,
) -> Result<get_public_rooms::v1::Response> {
if !services
.globals
.allow_public_room_directory_over_federation()
.config
.allow_public_room_directory_over_federation
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
}
+51 -63
View File
@@ -1,17 +1,15 @@
use std::collections::BTreeMap;
use axum::extract::State;
use futures::StreamExt;
use futures::{
StreamExt,
future::{join, join5},
};
use get_profile_information::v1::ProfileField;
use rand::seq::SliceRandom;
use ruma::{
OwnedServerName,
api::{
client::error::ErrorKind,
federation::query::{get_profile_information, get_room_information},
},
api::federation::query::{get_profile_information, get_room_information},
};
use tuwunel_core::{Error, Result, err};
use tuwunel_core::{Err, Result, err, utils::future::TryExtExt};
use crate::Ruma;
@@ -60,48 +58,38 @@ pub(crate) async fn get_profile_information_route(
State(services): State<crate::State>,
body: Ruma<get_profile_information::v1::Request>,
) -> Result<get_profile_information::v1::Response> {
use get_profile_information::v1::Response;
if !services
.server
.config
.allow_inbound_profile_lookup_federation_requests
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
return Err!(Request(Forbidden(
"Profile lookup over federation is not allowed on this homeserver.",
));
)));
}
if !services
.globals
.server_is_ours(body.user_id.server_name())
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"User does not belong to this server.",
));
return Err!(Request(InvalidParam("User does not belong to this server.",)));
}
let mut displayname = None;
let mut avatar_url = None;
let mut blurhash = None;
let mut tz = None;
let mut custom_profile_fields = BTreeMap::new();
match &body.field {
| Some(ProfileField::DisplayName) => {
displayname = services
.users
.displayname(&body.user_id)
.await
.ok();
},
| Some(ProfileField::AvatarUrl) => {
avatar_url = services
.users
.avatar_url(&body.user_id)
.await
.ok();
blurhash = services.users.blurhash(&body.user_id).await.ok();
| Some(ProfileField::AvatarUrl | ProfileField::DisplayName) => {
let avatar_url = services.users.avatar_url(&body.user_id).ok();
let displayname = services.users.displayname(&body.user_id).ok();
let (avatar_url, displayname) = join(avatar_url, displayname).await;
Ok(Response {
avatar_url,
displayname,
..Response::default()
})
},
| Some(custom_field) => {
if let Ok(value) = services
@@ -109,39 +97,39 @@ pub(crate) async fn get_profile_information_route(
.profile_key(&body.user_id, custom_field.as_str())
.await
{
custom_profile_fields.insert(custom_field.to_string(), value);
Ok(Response {
custom_profile_fields: [(custom_field.to_string(), value)].into(),
..Response::default()
})
} else {
Ok(Response::default())
}
},
| None => {
displayname = services
.users
.displayname(&body.user_id)
.await
.ok();
avatar_url = services
.users
.avatar_url(&body.user_id)
.await
.ok();
blurhash = services.users.blurhash(&body.user_id).await.ok();
tz = services.users.timezone(&body.user_id).await.ok();
custom_profile_fields = services
let avatar_url = services.users.avatar_url(&body.user_id).ok();
let blurhash = services.users.blurhash(&body.user_id).ok();
let displayname = services.users.displayname(&body.user_id).ok();
let tz = services.users.timezone(&body.user_id).ok();
let custom_profile_fields = services
.users
.all_profile_keys(&body.user_id)
.collect()
.await;
.collect();
let (avatar_url, blurhash, custom_profile_fields, displayname, tz) =
join5(avatar_url, blurhash, custom_profile_fields, displayname, tz).await;
Ok(Response {
avatar_url,
blurhash,
custom_profile_fields,
displayname,
tz,
..Response::default()
})
},
}
// services.users.timezone will collect the MSC4175 timezone key if it exists
custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz");
Ok(get_profile_information::v1::Response {
displayname,
avatar_url,
blurhash,
tz,
custom_profile_fields,
})
}
+1 -2
View File
@@ -161,12 +161,10 @@ async fn handle(
})
.try_flatten()
.try_collect()
.boxed()
.await?;
// evaluate edus after pdus, at least for now.
edus.for_each_concurrent(automatic_width(), |edu| handle_edu(services, client, origin, edu))
.boxed()
.await;
Ok(results)
@@ -205,6 +203,7 @@ async fn handle_room(
Ok((event_id, result))
})
.try_collect()
.boxed()
.await
}
+47 -37
View File
@@ -3,7 +3,7 @@
use std::borrow::Borrow;
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryStreamExt, future::try_join3};
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join4};
use ruma::{
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName,
api::federation::membership::create_join_event,
@@ -189,6 +189,14 @@ async fn create_join_event(
)
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
// Prestart state gather here since it doesn't involve the new join event.
let state_ids = services
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect::<Vec<OwnedEventId>>()
.boxed();
let mutex_lock = services
.event_handler
.mutex_federation
@@ -204,49 +212,51 @@ async fn create_join_event(
drop(mutex_lock);
let state_ids: Vec<OwnedEventId> = services
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect()
.await;
let starting_events = state_ids.iter().map(Borrow::borrow);
let auth_chain = services
.auth_chain
.event_ids_iter(room_id, starting_events)
.broad_and_then(async |event_id| services.timeline.get_pdu_json(&event_id).await)
.broad_and_then(|pdu| {
services
.federation
.format_pdu_into(pdu, Some(&room_version_id))
.map(Ok)
})
.try_collect();
let state = state_ids
.iter()
.try_stream()
.broad_and_then(|event_id| services.timeline.get_pdu_json(event_id))
.broad_and_then(|pdu| {
services
.federation
.format_pdu_into(pdu, Some(&room_version_id))
.map(Ok)
})
.try_collect();
// Join event for new server.
let event = services
.federation
.format_pdu_into(value, Some(&room_version_id))
.map(Some)
.map(Ok);
let (auth_chain, state, event) = try_join3(auth_chain, state, event).await?;
// Join event revealed to existing servers.
let broadcast = services.sending.send_pdu_room(room_id, &pdu_id);
services
.sending
.send_pdu_room(room_id, &pdu_id)
// Wait for state gather which the remaining operations depend on.
let state_ids = state_ids.await;
let auth_heads = state_ids.iter().map(Borrow::borrow);
let into_federation_format = |pdu| {
services
.federation
.format_pdu_into(pdu, Some(&room_version_id))
.map(Ok)
};
let auth_chain = services
.auth_chain
.event_ids_iter(room_id, auth_heads)
.broad_and_then(async |event_id| {
services
.timeline
.get_pdu_json(&event_id)
.and_then(into_federation_format)
.await
})
.try_collect();
let state = state_ids
.iter()
.try_stream()
.broad_and_then(|event_id| {
services
.timeline
.get_pdu_json(event_id)
.and_then(into_federation_format)
})
.try_collect();
let (auth_chain, state, event, ()) = try_join4(auth_chain, state, event, broadcast)
.boxed()
.await?;
Ok(create_join_event::v1::RoomState { auth_chain, state, event })
+8 -8
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use futures::FutureExt;
use futures::{FutureExt, future::try_join};
use ruma::{
OwnedServerName, OwnedUserId,
RoomVersionId::*,
@@ -174,16 +174,16 @@ pub(crate) async fn create_knock_event_v1_route(
drop(mutex_lock);
services
let broadcast = services
.sending
.send_pdu_room(&body.room_id, &pdu_id)
.await?;
.send_pdu_room(&body.room_id, &pdu_id);
let knock_room_state = services.state.summary_stripped(&pdu).map(Ok);
let (knock_room_state, ()) = try_join(knock_room_state, broadcast).await?;
Ok(create_knock_event::v1::Response {
knock_room_state: services
.state
.summary_stripped(&pdu)
.await
knock_room_state: knock_room_state
.into_iter()
.map(Into::into)
.collect(),
+46 -25
View File
@@ -1,9 +1,18 @@
use std::{borrow::Borrow, iter::once};
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryStreamExt};
use futures::{
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
future::{join, try_join},
};
use ruma::{OwnedEventId, api::federation::event::get_room_state};
use tuwunel_core::{Result, at, err, utils::IterStream};
use tuwunel_core::{
Result, at, err,
utils::{
future::TryExtExt,
stream::{IterStream, TryBroadbandExt},
},
};
use super::AccessCheck;
use crate::Ruma;
@@ -25,43 +34,55 @@ pub(crate) async fn get_room_state_route(
.await?;
let shortstatehash = services
.state_accessor
.state
.pdu_shortstatehash(&body.event_id)
.await
.map_err(|_| err!(Request(NotFound("PDU state not found."))))?;
let state_ids: Vec<OwnedEventId> = services
let state_ids = services
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect()
.await;
.collect::<Vec<OwnedEventId>>();
let pdus = state_ids
.iter()
.try_stream()
.and_then(|id| services.timeline.get_pdu_json(id))
.and_then(|pdu| {
services
.federation
.format_pdu_into(pdu, None)
.map(Ok)
})
.try_collect()
.await?;
let room_version = services
.state
.get_room_version(&body.room_id)
.ok();
let (room_version, state_ids) = join(room_version, state_ids).await;
let into_federation_format = |pdu| {
services
.federation
.format_pdu_into(pdu, room_version.as_ref())
.map(Ok)
};
let auth_chain = services
.auth_chain
.event_ids_iter(&body.room_id, once(body.event_id.borrow()))
.and_then(async |id| services.timeline.get_pdu_json(&id).await)
.and_then(|pdu| {
.broad_and_then(async |id| {
services
.federation
.format_pdu_into(pdu, None)
.map(Ok)
.timeline
.get_pdu_json(&id)
.and_then(into_federation_format)
.await
})
.try_collect()
.await?;
.try_collect();
let pdus = state_ids
.iter()
.try_stream()
.broad_and_then(|id| {
services
.timeline
.get_pdu_json(id)
.and_then(into_federation_format)
})
.try_collect();
let (auth_chain, pdus) = try_join(auth_chain, pdus).await?;
Ok(get_room_state::v1::Response { auth_chain, pdus })
}
+12 -11
View File
@@ -1,7 +1,7 @@
use std::{borrow::Borrow, iter::once};
use axum::extract::State;
use futures::{StreamExt, TryStreamExt};
use futures::{FutureExt, StreamExt, TryStreamExt, future::try_join};
use ruma::{OwnedEventId, api::federation::event::get_room_state_ids};
use tuwunel_core::{Result, at, err};
@@ -26,23 +26,24 @@ pub(crate) async fn get_room_state_ids_route(
.await?;
let shortstatehash = services
.state_accessor
.state
.pdu_shortstatehash(&body.event_id)
.await
.map_err(|_| err!(Request(NotFound("Pdu state not found."))))?;
let pdu_ids: Vec<OwnedEventId> = services
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect()
.await;
let auth_chain_ids = services
.auth_chain
.event_ids_iter(&body.room_id, once(body.event_id.borrow()))
.try_collect()
.await?;
.try_collect();
let pdu_ids = services
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect::<Vec<OwnedEventId>>()
.map(Ok);
let (auth_chain_ids, pdu_ids) = try_join(auth_chain_ids, pdu_ids).await?;
Ok(get_room_state_ids::v1::Response { auth_chain_ids, pdu_ids })
}
+2 -2
View File
@@ -44,7 +44,7 @@ pub(crate) async fn get_devices_route(
let device_id = metadata.device_id.clone();
let device_id_clone = device_id.clone();
let device_id_string = device_id.as_str().to_owned();
let device_display_name = if services.globals.allow_device_name_federation() {
let device_display_name = if services.config.allow_device_name_federation {
metadata.display_name.clone()
} else {
Some(device_id_string)
@@ -95,7 +95,7 @@ pub(crate) async fn get_keys_route(
None,
&body.device_keys,
|u| Some(u.server_name()) == body.origin.as_deref(),
services.globals.allow_device_name_federation(),
services.config.allow_device_name_federation,
)
.await?;
+10 -3
View File
@@ -11,6 +11,7 @@ version.workspace = true
[lib]
path = "mod.rs"
bench = false
crate-type = [
"rlib",
# "dylib",
@@ -20,9 +21,7 @@ crate-type = [
brotli_compression = [
"reqwest/brotli",
]
tuwunel_mods = [
"dep:libloading"
]
console = []
gzip_compression = [
"reqwest/gzip",
]
@@ -48,6 +47,9 @@ release_max_log_level = [
"log/release_max_level_info",
]
sentry_telemetry = []
tuwunel_mods = [
"dep:libloading"
]
zstd_compression = [
"reqwest/zstd",
]
@@ -112,9 +114,14 @@ url.workspace = true
nix.workspace = true
[dev-dependencies]
criterion.workspace = true
insta.workspace = true
maplit.workspace = true
similar.workspace = true
[lints]
workspace = true
[[bench]]
name = "state_res"
harness = false
@@ -1,14 +1,10 @@
#![cfg_attr(not(tuwunel_bench), allow(unused_imports, dead_code))]
#[cfg(tuwunel_bench)]
extern crate test;
use std::{
borrow::Borrow,
collections::HashMap,
sync::atomic::{AtomicU64, Ordering::SeqCst},
};
use criterion::{Criterion, async_executor::FuturesExecutor, criterion_group, criterion_main};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, UserId,
events::{
@@ -24,36 +20,41 @@
json,
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
};
use super::{AuthSet, StateMap, test_utils::not_found};
use crate::{
Result,
matrix::{Event, EventHash, PduEvent, event::TypeExt},
use tuwunel_core::{
Result, err,
matrix::{
Event, EventHash, PduEvent,
event::TypeExt,
state_res::{AuthSet, StateMap},
},
utils::stream::IterStream,
};
criterion_group!(
benches,
lexico_topo_sort,
resolution_shallow_auth_chain,
resolve_deeper_event_set
);
criterion_main!(benches);
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
#[cfg(tuwunel_bench)]
#[cfg_attr(tuwunel_bench, bench)]
fn lexico_topo_sort(c: &mut test::Bencher) {
use maplit::{hashmap, hashset};
fn lexico_topo_sort(c: &mut Criterion) {
c.bench_function("lexico_topo_sort", |c| {
use maplit::{hashmap, hashset};
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let graph = hashmap! {
event_id("l") => hashset![event_id("o")],
event_id("m") => hashset![event_id("n"), event_id("o")],
event_id("n") => hashset![event_id("o")],
event_id("o") => hashset![], // "o" has zero outgoing edges but 4 incoming edges
event_id("p") => hashset![event_id("o")],
};
let graph = hashmap! {
event_id("l") => hashset![event_id("o")],
event_id("m") => hashset![event_id("n"), event_id("o")],
event_id("n") => hashset![event_id("o")],
event_id("o") => hashset![], // "o" has zero outgoing edges but 4 incoming edges
event_id("p") => hashset![event_id("o")],
};
c.iter(move || {
rt.block_on(async {
_ = super::topological_sort(&graph, &async |_id| {
c.to_async(FuturesExecutor).iter(async || {
_ = tuwunel_core::matrix::state_res::topological_sort(&graph, &async |_id| {
Ok((int!(0).into(), MilliSecondsSinceUnixEpoch(uint!(0))))
})
.await;
@@ -61,131 +62,129 @@ fn lexico_topo_sort(c: &mut test::Bencher) {
});
}
#[cfg(tuwunel_bench)]
#[cfg_attr(tuwunel_bench, bench)]
fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
fn resolution_shallow_auth_chain(c: &mut Criterion) {
c.bench_function("resolution_shallow_auth_chain", |c| {
let mut store = TestStore(maplit::hashmap! {});
let mut store = TestStore(maplit::hashmap! {});
// build up the DAG
let (state_at_bob, state_at_charlie, _) = store.set_up();
// build up the DAG
let (state_at_bob, state_at_charlie, _) = store.set_up();
let rules = RoomVersionId::V6.rules().unwrap();
let ev_map = store.0.clone();
let state_sets = [state_at_bob, state_at_charlie];
let auth_chains = state_sets
.iter()
.map(|map| {
store
.auth_event_ids(room_id(), map.values().cloned().collect())
.unwrap()
})
.collect::<Vec<_>>();
let rules = RoomVersionId::V6.rules().unwrap();
let ev_map = store.0.clone();
let state_sets = [state_at_bob, state_at_charlie];
let auth_chains = state_sets
.iter()
.map(|map| {
store
.auth_event_ids(room_id(), map.values().cloned().collect())
.unwrap()
})
.collect::<Vec<_>>();
let func = async || {
if let Err(e) = tuwunel_core::matrix::state_res::resolve(
&rules,
state_sets.clone().into_iter().stream(),
auth_chains.clone().into_iter().stream(),
&async |id| {
ev_map
.get(&id)
.cloned()
.ok_or(err!(Request(NotFound("Not Found"))))
},
&async |id| ev_map.contains_key(&id),
false,
)
.await
{
panic!("{e}")
}
};
let func = async || {
if let Err(e) = super::resolve(
&rules,
state_sets.clone().into_iter().stream(),
auth_chains.clone().into_iter().stream(),
&async |id| ev_map.get(&id).cloned().ok_or_else(not_found),
&async |id| ev_map.contains_key(&id),
false,
)
.await
{
panic!("{e}")
}
};
c.iter(move || {
rt.block_on(async {
c.to_async(FuturesExecutor).iter(async || {
func().await;
});
});
}
#[cfg(tuwunel_bench)]
#[cfg_attr(tuwunel_bench, bench)]
fn resolve_deeper_event_set(c: &mut test::Bencher) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
fn resolve_deeper_event_set(c: &mut Criterion) {
c.bench_function("resolver_deeper_event_set", |c| {
let mut inner = INITIAL_EVENTS();
let ban = BAN_STATE_SET();
let mut inner = INITIAL_EVENTS();
let ban = BAN_STATE_SET();
inner.extend(ban);
let store = TestStore(inner.clone());
inner.extend(ban);
let store = TestStore(inner.clone());
let state_set_a = [
&inner[&event_id("CREATE")],
&inner[&event_id("IJR")],
&inner[&event_id("IMA")],
&inner[&event_id("IMB")],
&inner[&event_id("IMC")],
&inner[&event_id("MB")],
&inner[&event_id("PA")],
]
.iter()
.map(|ev| {
(
ev.event_type()
.with_state_key(ev.state_key().unwrap()),
ev.event_id().to_owned(),
)
})
.collect::<StateMap<_>>();
let state_set_b = [
&inner[&event_id("CREATE")],
&inner[&event_id("IJR")],
&inner[&event_id("IMA")],
&inner[&event_id("IMB")],
&inner[&event_id("IMC")],
&inner[&event_id("IME")],
&inner[&event_id("PA")],
]
.iter()
.map(|ev| {
(
ev.event_type()
.with_state_key(ev.state_key().unwrap()),
ev.event_id().to_owned(),
)
})
.collect::<StateMap<_>>();
let rules = RoomVersionId::V6.rules().unwrap();
let state_sets = [state_set_a, state_set_b];
let auth_chains = state_sets
let state_set_a = [
&inner[&event_id("CREATE")],
&inner[&event_id("IJR")],
&inner[&event_id("IMA")],
&inner[&event_id("IMB")],
&inner[&event_id("IMC")],
&inner[&event_id("MB")],
&inner[&event_id("PA")],
]
.iter()
.map(|map| {
store
.auth_event_ids(room_id(), map.values().cloned().collect())
.unwrap()
.map(|ev| {
(
ev.event_type()
.with_state_key(ev.state_key().unwrap()),
ev.event_id().to_owned(),
)
})
.collect::<Vec<_>>();
.collect::<StateMap<_>>();
let func = async || {
if let Err(e) = super::resolve(
&rules,
state_sets.clone().into_iter().stream(),
auth_chains.clone().into_iter().stream(),
&async |id| inner.get(&id).cloned().ok_or_else(not_found),
&async |id| inner.contains_key(&id),
false,
)
.await
{
panic!("{e}")
}
};
let state_set_b = [
&inner[&event_id("CREATE")],
&inner[&event_id("IJR")],
&inner[&event_id("IMA")],
&inner[&event_id("IMB")],
&inner[&event_id("IMC")],
&inner[&event_id("IME")],
&inner[&event_id("PA")],
]
.iter()
.map(|ev| {
(
ev.event_type()
.with_state_key(ev.state_key().unwrap()),
ev.event_id().to_owned(),
)
})
.collect::<StateMap<_>>();
c.iter(move || {
rt.block_on(async {
let rules = RoomVersionId::V6.rules().unwrap();
let state_sets = [state_set_a, state_set_b];
let auth_chains = state_sets
.iter()
.map(|map| {
store
.auth_event_ids(room_id(), map.values().cloned().collect())
.unwrap()
})
.collect::<Vec<_>>();
let func = async || {
if let Err(e) = tuwunel_core::matrix::state_res::resolve(
&rules,
state_sets.clone().into_iter().stream(),
auth_chains.clone().into_iter().stream(),
&async |id| {
inner
.get(&id)
.cloned()
.ok_or(err!(Request(NotFound("Not Found"))))
},
&async |id| inner.contains_key(&id),
false,
)
.await
{
panic!("{e}")
}
};
c.to_async(FuturesExecutor).iter(async || {
func().await;
});
});
@@ -204,7 +203,7 @@ fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
self.0
.get(event_id)
.cloned()
.ok_or_else(not_found)
.ok_or(err!(Request(NotFound("Not Found"))))
}
/// Returns the events that correspond to the `event_ids` sorted in the same
@@ -433,13 +432,13 @@ fn to_pdu_event<S>(
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let prev_events = prev_events
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let state_key = state_key.map(ToOwned::to_owned);
PduEvent {
@@ -458,8 +457,8 @@ fn to_pdu_event<S>(
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
#[cfg(test)]
rejected: false,
//#[cfg(test)]
//rejected: false,
}
}
+76 -3
View File
@@ -76,14 +76,14 @@ pub struct Config {
/// WIPE.
///
/// example: "girlboss.ceo"
#[cfg_attr(test, serde(default = "default_server_name"))]
pub server_name: OwnedServerName,
/// This is the only directory where tuwunel will save its data, including
/// media. Note: this was previously "/var/lib/matrix-conduit".
///
/// YOU NEED TO EDIT THIS.
///
/// example: "/var/lib/tuwunel"
/// default: "/var/lib/tuwunel"
#[serde(default = "default_database_path")]
pub database_path: PathBuf,
/// Text which will be added to the end of the user's displayname upon
@@ -385,6 +385,18 @@ pub struct Config {
#[serde(default)]
pub dns_passthru_appservices: bool,
/// Enable or disable case randomization for DNS queries. This is a security
/// mitigation where answer spoofing is prevented by having to exactly match
/// the question. Occasional errors seen in logs which may have lead you
/// here tend to be from overloading DNS. Nevertheless for servers which
/// are truly incapable this can be set to false.
///
/// This currently defaults to false due to user reports regarding some
/// popular DNS caches which may or may not be patched soon. It may again
/// default to true in an upcoming release.
#[serde(default)]
pub dns_case_randomization: bool,
/// Max request size for file uploads in bytes. Defaults to 20MB.
///
/// default: 20971520
@@ -857,6 +869,10 @@ pub struct Config {
#[serde(default = "true_fn", alias = "log_colours")]
pub log_colors: bool,
/// Sets the log format to compact mode.
#[serde(default)]
pub log_compact: bool,
/// Configures the span events which will be outputted with the log.
///
/// default: "none"
@@ -876,6 +892,15 @@ pub struct Config {
#[serde(default)]
pub log_thread_ids: bool,
/// Redirects logging to standard error (stderr). The default is false for
/// stdout. For those using our systemd features the redirection to stderr
/// occurs as necessary and setting this option should not be required. We
/// offer this option for all other users who desire such redirection.
///
/// default: false
#[serde(default)]
pub log_to_stderr: bool,
/// OpenID token expiration/TTL in seconds.
///
/// These are the OpenID tokens that are primarily used for Matrix account
@@ -1252,6 +1277,20 @@ pub struct Config {
#[serde(default = "default_rocksdb_stats_level")]
pub rocksdb_stats_level: u8,
/// Erases data no longer reachable in the current schema. The developers
/// expect this to be set to true which simplifies the schema and prevents
/// accumulation of old schemas remaining in the codebase forever. If this
/// is set to false, old columns which are not described in the current
/// schema will be ignored rather than erased, leaking their space.
///
/// This can be set to false when moving between versions in ways which are
/// not recommended or otherwise forbidden, or for diagnostic and
/// development purposes; requiring preservation across such movements.
///
/// default: true
#[serde(default = "true_fn")]
pub rocksdb_drop_missing_columns: bool,
/// This is a password that can be configured that will let you login to the
/// server bot account (currently `@conduit`) for emergency troubleshooting
/// purposes such as recovering/recreating your admin room, or inviting
@@ -1835,6 +1874,12 @@ pub struct Config {
#[serde(default)]
pub tokio_console: bool,
/// Arbitrary argument vector for integration testing. Functionality in the
/// server is altered or informed for the requirements of integration tests.
/// - "smoke" performs a shutdown after startup admin commands rather than
/// hanging on client handling.
///
/// default: []
#[serde(default)]
pub test: BTreeSet<String>,
@@ -2069,12 +2114,25 @@ pub struct WellKnownConfig {
/// example: "matrix.example.com:443"
pub server: Option<OwnedServerName>,
/// The URL of the support web page. This and the below generate the content
/// of `/.well-known/matrix/support`.
///
/// example: "https://example.com/support"
pub support_page: Option<Url>,
/// The name of the support role.
///
/// example: "m.role.admin"
pub support_role: Option<ContactRole>,
/// The email address for the above support role.
///
/// example: "admin@example.com"
pub support_email: Option<String>,
/// The Matrix User ID for the above support role.
///
/// example "@admin:example.com"
pub support_mxid: Option<OwnedUserId>,
}
@@ -2123,6 +2181,8 @@ pub struct LdapConfig {
/// Root of the searches.
///
/// example: "ou=users,dc=example,dc=org"
///
/// default:
#[serde(default)]
pub base_dn: String,
@@ -2136,6 +2196,8 @@ pub struct LdapConfig {
///
/// example: "cn=ldap-reader,dc=example,dc=org" or
/// "cn={username},ou=users,dc=example,dc=org"
///
/// default: ""
#[serde(default)]
pub bind_dn: Option<String>,
@@ -2143,6 +2205,8 @@ pub struct LdapConfig {
/// `bind_dn`.
///
/// The server must be able to access the file, and it must not be empty.
///
/// default: ""
#[serde(default)]
pub bind_password_file: Option<PathBuf>,
@@ -2186,6 +2250,8 @@ pub struct LdapConfig {
/// Defaults to `base_dn` if empty.
///
/// example: "ou=admins,dc=example,dc=org"
///
/// default:
#[serde(default)]
pub admin_base_dn: String,
@@ -2198,6 +2264,8 @@ pub struct LdapConfig {
/// entered username for more complex filters.
///
/// example: "(objectClass=tuwunelAdmin)" or "(uid={username})"
///
/// default:
#[serde(default)]
pub admin_filter: String,
}
@@ -2513,6 +2581,11 @@ pub fn check(&self) -> Result<(), Error> { check(self) }
fn true_fn() -> bool { true }
#[cfg(test)]
fn default_server_name() -> OwnedServerName { ruma::owned_server_name!("localhost") }
fn default_database_path() -> PathBuf { "/var/lib/tuwunel".to_owned().into() }
fn default_address() -> ListeningAddr {
ListeningAddr {
addrs: Right(vec![Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]),
+2 -7
View File
@@ -7,13 +7,8 @@
use crate::{at, is_equal_to};
/// Partially supported non-compliant room versions
pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] = &[
RoomVersionId::V1,
RoomVersionId::V2,
RoomVersionId::V3,
RoomVersionId::V4,
RoomVersionId::V5,
];
pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] =
&[RoomVersionId::V2, RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
/// Supported and stable room versions
pub const STABLE_ROOM_VERSIONS: &[RoomVersionId] = &[
+19 -14
View File
@@ -14,7 +14,7 @@
registry::LookupSpan,
};
use crate::{Config, Result, apply};
use crate::{Config, Result, apply, is_equal_to};
static SYSTEMD_MODE: LazyLock<bool> =
LazyLock::new(|| env::var("SYSTEMD_EXEC_PID").is_ok() && env::var("JOURNAL_STREAM").is_ok());
@@ -28,13 +28,13 @@ pub struct ConsoleWriter {
impl ConsoleWriter {
#[must_use]
pub fn new(_config: &Config) -> Self {
pub fn new(config: &Config) -> Self {
let journal_stream = get_journal_stream();
Self {
stdout: io::stdout(),
stderr: io::stderr(),
_journal_stream: journal_stream.into(),
use_stderr: journal_stream.0 != 0,
use_stderr: journal_stream.0 != 0 || config.log_to_stderr,
}
}
}
@@ -64,23 +64,16 @@ fn flush(&mut self) -> io::Result<()> {
}
pub struct ConsoleFormat {
_compact: Format<Compact>,
full: Format<Full>,
pretty: Format<Pretty>,
full: Format<Full>,
compact: Format<Compact>,
compact_mode: bool,
}
impl ConsoleFormat {
#[must_use]
pub fn new(config: &Config) -> Self {
Self {
_compact: fmt::format()
.compact()
.with_ansi(config.log_colors),
full: Format::<Full>::default()
.with_thread_ids(config.log_thread_ids)
.with_ansi(config.log_colors),
pretty: fmt::format()
.pretty()
.with_ansi(config.log_colors)
@@ -90,6 +83,16 @@ pub fn new(config: &Config) -> Self {
.with_file(true)
.with_line_number(true)
.with_source_location(true),
full: Format::<Full>::default()
.with_thread_ids(config.log_thread_ids)
.with_ansi(config.log_colors),
compact: fmt::format()
.compact()
.with_ansi(config.log_colors),
compact_mode: config.log_compact,
}
}
}
@@ -108,9 +111,11 @@ fn format_event(
let is_debug = cfg!(debug_assertions)
&& event
.fields()
.any(|field| field.name() == "_debug");
.map(|field| field.name())
.any(is_equal_to!("_debug"));
match *event.metadata().level() {
| _ if self.compact_mode => self.compact.format_event(ctx, writer, event),
| Level::ERROR if !is_debug => self.pretty.format_event(ctx, writer, event),
| _ => self.full.format_event(ctx, writer, event),
}
+61 -4
View File
@@ -1,14 +1,17 @@
use ruma::api::client::filter::{RoomEventFilter, UrlFilter};
use ruma::{
RoomId, UserId,
api::client::filter::{Filter, RoomEventFilter, RoomFilter, UrlFilter},
};
use serde_json::Value;
use super::Event;
use crate::is_equal_to;
pub trait Matches<E: Event> {
fn matches(&self, event: &E) -> bool;
pub trait Matches<T> {
fn matches(&self, t: T) -> bool;
}
impl<E: Event> Matches<E> for &RoomEventFilter {
impl<E: Event> Matches<&E> for RoomEventFilter {
#[inline]
fn matches(&self, event: &E) -> bool {
if !matches_sender(event, self) {
@@ -31,6 +34,60 @@ fn matches(&self, event: &E) -> bool {
}
}
impl Matches<&RoomId> for RoomFilter {
#[inline]
fn matches(&self, room_id: &RoomId) -> bool {
if !matches_room_id(room_id, self) {
return false;
}
true
}
}
impl Matches<&UserId> for Filter {
#[inline]
fn matches(&self, user_id: &UserId) -> bool {
if !matches_user_id(user_id, self) {
return false;
}
true
}
}
fn matches_user_id(user_id: &UserId, filter: &Filter) -> bool {
if filter
.not_senders
.iter()
.any(is_equal_to!(user_id))
{
return false;
}
if let Some(senders) = filter.senders.as_ref() {
if !senders.iter().any(is_equal_to!(user_id)) {
return false;
}
}
true
}
fn matches_room_id(room_id: &RoomId, filter: &RoomFilter) -> bool {
if filter.not_rooms.iter().any(is_equal_to!(room_id)) {
return false;
}
if let Some(rooms) = filter.rooms.as_ref() {
if !rooms.iter().any(is_equal_to!(room_id)) {
return false;
}
}
true
}
fn matches_room<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
if filter
.not_rooms
+2 -3
View File
@@ -1,5 +1,5 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
use super::Event;
use crate::{Result, err, is_true};
@@ -43,8 +43,7 @@ pub(super) fn get_unsigned<T, E>(event: &E) -> Result<T>
{
event
.unsigned()
.as_ref()
.map(|raw| raw.get())
.map(RawJsonValue::get)
.map(serde_json::from_str)
.ok_or(err!(Request(NotFound("\"unsigned\" property not found in pdu"))))?
.map_err(|e| err!(Database("Failed to deserialize \"unsigned\" into value: {e}")))
+6 -1
View File
@@ -6,6 +6,11 @@
pub mod state_res;
pub use event::{Event, StateKey, TypeExt as EventTypeExt, TypeStateKey, state_key};
pub use pdu::{EventHash, Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId};
pub use pdu::{EventHash, Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId};
pub use room_version::{RoomVersion, RoomVersionRules};
pub use state_res::{StateMap, events};
pub type ShortStateKey = ShortId;
pub type ShortEventId = ShortId;
pub type ShortRoomId = ShortId;
pub type ShortId = u64;
+31 -20
View File
@@ -17,54 +17,55 @@
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use smallvec::SmallVec;
pub use self::{
Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
builder::{Builder, Builder as PduBuilder},
count::Count,
hashes::EventHashes as EventHash,
id::{ShortId, *},
id::Id,
raw_id::*,
};
use super::{Event, StateKey};
use super::{Event, ShortRoomId, StateKey};
use crate::Result;
/// Persistent Data Unit (Event)
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Pdu {
#[serde(rename = "type")]
pub kind: TimelineEventType,
pub content: Box<RawJsonValue>,
pub event_id: OwnedEventId,
pub room_id: OwnedRoomId,
pub sender: OwnedUserId,
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OwnedServerName>,
pub origin_server_ts: UInt,
#[serde(rename = "type")]
pub kind: TimelineEventType,
pub content: Box<RawJsonValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<StateKey>,
pub prev_events: Vec<OwnedEventId>,
pub depth: UInt,
pub auth_events: Vec<OwnedEventId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<OwnedEventId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unsigned: Option<Box<RawJsonValue>>,
pub prev_events: PrevEvents,
pub auth_events: AuthEvents,
pub origin_server_ts: UInt,
pub depth: UInt,
pub hashes: EventHash,
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OwnedServerName>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unsigned: Option<Box<RawJsonValue>>,
// BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signatures: Option<Box<RawJsonValue>>,
@@ -75,6 +76,16 @@ pub struct Pdu {
pub rejected: bool,
}
/// Tuned prev_events vector. Most events have one prev_event. Many events have
/// more but allocations for all of those cases still beats allocations for all
/// cases.
pub type PrevEvents = SmallVec<[OwnedEventId; 1]>;
/// Tuned auth_events vector. Average events have three auth events. It is
/// debatable whether this could be an ArrayVec but the realistic upper-bound is
/// too high and non-deterministic in the era of restricted-type rooms.
pub type AuthEvents = SmallVec<[OwnedEventId; 3]>;
/// The [maximum size allowed] for a PDU.
/// [maximum size allowed]: https://spec.matrix.org/latest/client-server-api/#size-limits
pub const MAX_PDU_BYTES: usize = 65_535;
+39 -11
View File
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, fmt};
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId,
@@ -10,7 +10,7 @@
use super::StateKey;
/// Build the start of a PDU in order to add it to the Database.
#[derive(Debug, Deserialize)]
#[derive(Deserialize)]
pub struct Builder {
#[serde(rename = "type")]
pub event_type: TimelineEventType,
@@ -30,6 +30,19 @@ pub struct Builder {
type Unsigned = BTreeMap<String, serde_json::Value>;
impl Default for Builder {
fn default() -> Self {
Self {
event_type: "m.room.message".into(),
content: Box::<RawJsonValue>::default(),
unsigned: None,
state_key: None,
redacts: None,
timestamp: None,
}
}
}
impl Builder {
pub fn state<S, T>(state_key: S, content: &T) -> Self
where
@@ -58,15 +71,30 @@ pub fn timeline<T>(content: &T) -> Self
}
}
impl Default for Builder {
fn default() -> Self {
Self {
event_type: "m.room.message".into(),
content: Box::<RawJsonValue>::default(),
unsigned: None,
state_key: None,
redacts: None,
timestamp: None,
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Builder");
d.field("type", &self.event_type);
if let Some(state_key) = self.state_key.as_ref() {
d.field("state_key", state_key);
}
if let Some(redacts) = self.redacts.as_ref() {
d.field("redacts", redacts);
}
if let Some(timestamp) = self.timestamp.as_ref() {
d.field("ts", timestamp);
}
if let Some(unsigned) = self.unsigned.as_ref() {
d.field("unsigned", unsigned);
}
d.field("content", &self.content);
d.finish()
}
}
+4
View File
@@ -17,6 +17,10 @@ pub enum Count {
}
impl Count {
#[inline]
#[must_use]
pub fn to_be_bytes(self) -> [u8; size_of::<u64>()] { self.into_unsigned().to_be_bytes() }
#[inline]
#[must_use]
pub fn from_unsigned(unsigned: u64) -> Self { Self::from_signed(unsigned as i64) }
+4 -10
View File
@@ -1,23 +1,17 @@
use super::{Count, RawId};
use crate::utils::u64_from_u8x8;
pub type ShortRoomId = ShortId;
pub type ShortEventId = ShortId;
pub type ShortStateKey = ShortId;
pub type ShortId = u64;
use super::{Count, RawId, ShortRoomId};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Id {
pub shortroomid: ShortRoomId,
pub shorteventid: Count,
pub count: Count,
}
impl From<RawId> for Id {
#[inline]
fn from(raw: RawId) -> Self {
Self {
shortroomid: u64_from_u8x8(raw.shortroomid()),
shorteventid: Count::from_unsigned(u64_from_u8x8(raw.shorteventid())),
shortroomid: u64::from_be_bytes(raw.shortroomid()),
count: Count::from_unsigned(u64::from_be_bytes(raw.count())),
}
}
}
+77 -39
View File
@@ -1,8 +1,12 @@
use std::fmt;
use arrayvec::ArrayVec;
use serde::{Deserialize, Deserializer};
use super::{Count, Id, ShortEventId, ShortId, ShortRoomId};
use super::{
super::{ShortId, ShortRoomId},
Count, Id,
};
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum RawId {
@@ -13,18 +17,24 @@ pub enum RawId {
type RawIdNormal = [u8; RawId::NORMAL_LEN];
type RawIdBackfilled = [u8; RawId::BACKFILLED_LEN];
struct RawIdVisitor;
const INT_LEN: usize = size_of::<ShortId>();
impl RawId {
const BACKFILLED_LEN: usize = size_of::<ShortRoomId>() + INT_LEN + size_of::<ShortEventId>();
const BACKFILLED_LEN: usize = size_of::<ShortRoomId>() + INT_LEN + size_of::<i64>();
const MAX_LEN: usize = Self::BACKFILLED_LEN;
const NORMAL_LEN: usize = size_of::<ShortRoomId>() + size_of::<ShortEventId>();
const NORMAL_LEN: usize = size_of::<ShortRoomId>() + size_of::<u64>();
#[inline]
#[must_use]
pub fn pdu_count(&self) -> Count {
let id: Id = (*self).into();
id.shorteventid
pub fn is_room_eq(self, other: Self) -> bool { self.shortroomid() == other.shortroomid() }
#[inline]
#[must_use]
pub fn pdu_count(self) -> Count {
let id: Id = self.into();
id.count
}
#[inline]
@@ -42,14 +52,14 @@ pub fn pdu_count(&self) -> Count {
#[inline]
#[must_use]
pub fn shorteventid(self) -> [u8; INT_LEN] {
pub fn count(self) -> [u8; INT_LEN] {
match self {
| Self::Normal(raw) => raw[INT_LEN..INT_LEN * 2]
.try_into()
.expect("normal raw shorteventid array from slice"),
.expect("normal raw indice array from slice"),
| Self::Backfilled(raw) => raw[INT_LEN * 2..INT_LEN * 3]
.try_into()
.expect("backfilled raw shorteventid array from slice"),
.expect("backfilled raw indice array from slice"),
}
}
@@ -70,9 +80,53 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
}
impl AsRef<[u8]> for RawId {
impl<'de> Deserialize<'de> for RawId {
#[inline]
fn as_ref(&self) -> &[u8] { self.as_bytes() }
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_bytes(RawIdVisitor)
}
}
impl serde::de::Visitor<'_> for RawIdVisitor {
type Value = RawId;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RawId byte array")
}
#[inline]
fn visit_bytes<E>(self, buf: &[u8]) -> Result<RawId, E> { Ok(RawId::from(buf)) }
}
impl From<Id> for RawId {
#[inline]
fn from(id: Id) -> Self {
const MAX_LEN: usize = RawId::MAX_LEN;
type RawVec = ArrayVec<u8, MAX_LEN>;
let mut vec = RawVec::new();
vec.extend(id.shortroomid.to_be_bytes());
id.count.debug_assert_valid();
match id.count {
| Count::Normal(count) => {
vec.extend(count.to_be_bytes());
Self::Normal(
vec.as_ref()
.try_into()
.expect("RawVec into RawId::Normal"),
)
},
| Count::Backfilled(count) => {
vec.extend(0_u64.to_be_bytes());
vec.extend(count.to_be_bytes());
Self::Backfilled(
vec.as_ref()
.try_into()
.expect("RawVec into RawId::Backfilled"),
)
},
}
}
}
impl From<&[u8]> for RawId {
@@ -94,33 +148,17 @@ fn from(id: &[u8]) -> Self {
}
}
impl From<Id> for RawId {
impl From<&[u8; Self::NORMAL_LEN]> for RawId {
#[inline]
fn from(id: Id) -> Self {
const MAX_LEN: usize = RawId::MAX_LEN;
type RawVec = ArrayVec<u8, MAX_LEN>;
let mut vec = RawVec::new();
vec.extend(id.shortroomid.to_be_bytes());
id.shorteventid.debug_assert_valid();
match id.shorteventid {
| Count::Normal(shorteventid) => {
vec.extend(shorteventid.to_be_bytes());
Self::Normal(
vec.as_ref()
.try_into()
.expect("RawVec into RawId::Normal"),
)
},
| Count::Backfilled(shorteventid) => {
vec.extend(0_u64.to_be_bytes());
vec.extend(shorteventid.to_be_bytes());
Self::Backfilled(
vec.as_ref()
.try_into()
.expect("RawVec into RawId::Backfilled"),
)
},
}
}
fn from(id: &[u8; Self::NORMAL_LEN]) -> Self { Self::Normal(*id) }
}
impl From<&[u8; Self::BACKFILLED_LEN]> for RawId {
#[inline]
fn from(id: &[u8; Self::BACKFILLED_LEN]) -> Self { Self::Backfilled(*id) }
}
impl AsRef<[u8]> for RawId {
#[inline]
fn as_ref(&self) -> &[u8] { self.as_bytes() }
}
@@ -669,8 +669,8 @@ async fn auth_event_in_different_room() {
content: to_raw_json_value(&json!({ "users": { alice(): 100 } })).unwrap(),
redacts: None,
unsigned: None,
auth_events: vec![event_id("CREATE"), event_id("IMA")],
prev_events: vec![event_id("IMA")],
auth_events: vec![event_id("CREATE"), event_id("IMA")].into(),
prev_events: vec![event_id("IMA")].into(),
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
@@ -807,8 +807,8 @@ async fn rejected_auth_event() {
content: to_raw_json_value(&json!({ "users": { alice(): 100 } })).unwrap(),
redacts: None,
unsigned: None,
auth_events: vec![event_id("CREATE"), event_id("IMA")],
prev_events: vec![event_id("IMA")],
auth_events: vec![event_id("CREATE"), event_id("IMA")].into(),
prev_events: vec![event_id("IMA")].into(),
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
@@ -893,12 +893,13 @@ async fn event_without_room_id() {
content: to_raw_json_value(&RoomMessageEventContent::text_plain("Hi!")).unwrap(),
redacts: None,
unsigned: None,
auth_events: vec![
auth_events: [
owned_event_id!("$CREATE"),
owned_event_id!("$IMA"),
owned_event_id!("$IPOWER"),
],
prev_events: vec![owned_event_id!("$IPOWER")],
]
.into(),
prev_events: [owned_event_id!("$IPOWER")].into(),
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
-2
View File
@@ -58,8 +58,6 @@
//! [necessary checks on receipt of a PDU]: https://spec.matrix.org/latest/server-server-api/#checks-performed-on-receipt-of-a-pdu
//! [ruma-events]: https://crates.io/crates/ruma-events
#[cfg(test)]
mod benches;
mod event_auth;
mod event_format;
pub mod events;
@@ -126,7 +126,7 @@ async fn mainline_position<Fetch, Fut, Pdu>(
) -> Result<usize>
where
Fetch: Fn(OwnedEventId) -> Fut + Sync,
Fut: Future<Output = Result<Pdu>>,
Fut: Future<Output = Result<Pdu>> + Send,
Pdu: Event,
{
let mut current_event = Some(event.clone());
@@ -154,7 +154,7 @@ async fn get_power_levels_auth_event<Fetch, Fut, Pdu>(
) -> Result<Option<Pdu>>
where
Fetch: Fn(OwnedEventId) -> Fut + Sync,
Fut: Future<Output = Result<Pdu>>,
Fut: Future<Output = Result<Pdu>> + Send,
Pdu: Event,
{
let power_level_event = event
+2 -2
View File
@@ -163,7 +163,7 @@ async fn topic_basic() {
.finish(),
);
let events = &[
let events = vec![
to_init_pdu_event(
"T1",
alice(),
@@ -219,7 +219,7 @@ async fn topic_basic() {
.map(event_id)
.collect::<Vec<_>>();
do_check(events, edges, expected_state_ids).await;
do_check(&events, edges, expected_state_ids).await;
}
#[tokio::test]
+13 -13
View File
@@ -428,13 +428,13 @@ pub(super) fn to_init_pdu_event(
content: Box<RawJsonValue>,
) -> PduEvent {
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
let state_key = state_key.map(ToOwned::to_owned);
let id = if id.contains('$') {
id.to_owned()
} else {
format!("${id}:foo")
};
let state_key = state_key.map(ToOwned::to_owned);
PduEvent {
event_id: id.try_into().unwrap(),
room_id: room_id().to_owned(),
@@ -446,8 +446,8 @@ pub(super) fn to_init_pdu_event(
content,
redacts: None,
unsigned: None,
auth_events: vec![],
prev_events: vec![],
auth_events: Default::default(),
prev_events: Default::default(),
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
@@ -468,6 +468,7 @@ pub(super) fn to_pdu_event<S>(
S: AsRef<str>,
{
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
let state_key = state_key.map(ToOwned::to_owned);
let id = if id.contains('$') {
id.to_owned()
} else {
@@ -477,14 +478,13 @@ pub(super) fn to_pdu_event<S>(
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let prev_events = prev_events
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let state_key = state_key.map(ToOwned::to_owned);
PduEvent {
event_id: id.try_into().unwrap(),
room_id: room_id().to_owned(),
@@ -528,18 +528,18 @@ fn event_id(id: &str) -> OwnedEventId {
}
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
let state_key = state_key.map(ToOwned::to_owned);
let auth_events = auth_events
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let prev_events = prev_events
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let state_key = state_key.map(ToOwned::to_owned);
PduEvent {
event_id: event_id(id),
room_id: hydra_room_id().to_owned(),
@@ -581,12 +581,12 @@ pub(super) fn room_redaction_pdu_event<S>(
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
let prev_events = prev_events
.iter()
.map(AsRef::as_ref)
.map(event_id)
.collect::<Vec<_>>();
.collect();
PduEvent {
event_id: id.try_into().unwrap(),
@@ -636,8 +636,8 @@ pub(super) fn room_create_hydra_pdu_event(
content,
redacts: None,
unsigned: None,
auth_events: vec![],
prev_events: vec![],
auth_events: Default::default(),
prev_events: Default::default(),
depth: uint!(0),
hashes: EventHash::default(),
signatures: None,
+1 -4
View File
@@ -104,10 +104,7 @@ pub fn shutdown(&self) -> Result {
}
pub fn signal(&self, sig: &'static str) -> Result {
if let Err(e) = self.signal.send(sig) {
return Err!("Failed to send signal: {e}");
}
self.signal.send(sig).ok();
Ok(())
}

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