Compare commits

..

143 Commits

Author SHA1 Message Date
strawberry c29197b3f4 ci: dont publish -rc tags as latest docker branches
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-16 00:44:47 -04:00
Jason Volk 739eab46d5 refactor thumbnail math
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:40:18 +00:00
Jason Volk 923a98eb66 partially revert dc18f89c0b for now
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:40:18 +00:00
Jason Volk 4430e4dee0 de-global some services()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk d67f19a55d cleanup some error callsites
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk b903b46d16 split thumbnailing related into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 167559bb27 rename FileMeta::file to Option<content>
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 838e4b9d8d log more details for panic in tower handler
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 038b71fc9d add state to router
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 720fbd09c2 move routes into api router top level
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk c42cb90dd3 simplify result handler / 405 error interposition
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 5950355348 break from wait loop on empty taskset
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
strawberry f79bd2ac72 bump ruma-identifiers-validation to c51ccb2c68d2e3557eb12b1a49036531711ec0e5
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 80ec0e31b1 bump tracing fork
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry bda44b16b1 ci: run nix dynamic build test with DIRENV_DEVSHELL=dynamic
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry e2280aa1a5 ci: try removing --impure
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry bdf2de076a ci: set NIX_CONFIG with our binary caches for the earlier CI steps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 1797fec3c9 nix: disable USE_RTTI for rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 188fa5a073 patch rustyline-async and use no default features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry f0c63c539b nix: dont build more unnecessary tests/tools for rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry 649e9da1f8 nix: try to make armv8.2-a / cortex-a55 optimised builds if target is aarch64
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry df28359a19 nix: dont build rocksdb core tools (ldb / sst_dump) (-DWITH_CORE_TOOLS=1)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry 9370e93a8d nix: try to make x86-64-v2 optimised builds if target is x86_64
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry bdd5845490 docs: suggest recursive chmod
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry bacffd6174 rename conduit_cache_capacity_modifier to cache_capacity_modifier
this prefix causes you to require setting the environment variable
to `CONDUWUIT_CONDUIT_CACHE_CAPACITY_MODIFIER`

alias this so we dont break any configs

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:15 -04:00
strawberry a1bfd7a018 nix: dont include experimental feature on all-features builds
this is just future infra for it

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 7009f56a7a fix user directory publishing access controls
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 2c0bfac43e nix: bump flake.lock and rocksdb to v9.4.0
• Updated input 'attic':
    'github:zhaofengli/attic/717cc95983cdc357bc347d70be20ced21f935843' (2024-06-01)
  → 'github:zhaofengli/attic/6139576a3ce6bb992e0f6c3022528ec233e45f00' (2024-07-09)
• Updated input 'complement':
    'github:matrix-org/complement/bc97f1ddc1cd7485faf80c8935ee2641f3e1b57c' (2024-07-02)
  → 'github:matrix-org/complement/0d14432e010482ea9e13a6f7c47c1533c0c9d62f' (2024-07-10)
• Updated input 'crane':
    'github:ipetkov/crane/0aed560c5c0a61c9385bddff471a13036203e11c' (2024-07-06)
  → 'github:ipetkov/crane/2d83156f23c43598cf44e152c33a59d3892f8b29' (2024-07-09)
• Updated input 'fenix':
    'github:nix-community/fenix/abc0549e3560189462a7d394cc9d50af4608d103' (2024-07-08)
  → 'github:nix-community/fenix/5087b12a595ee73131a944d922f24d81dae05725' (2024-07-13)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/a5b21ea0aa644dffd7cf958b43f11f221d53404e' (2024-07-07)
  → 'github:rust-lang/rust-analyzer/ffbc5ad993d5cd2f3b8bcf9a511165470944ab91' (2024-07-11)
• Updated input 'liburing':
    'github:axboe/liburing/7b3245583069bd481190c9da18f22e9fc8c3a805' (2024-06-22)
  → 'github:axboe/liburing/1d674f83b7d0f07553ac44d99a401b05853d9dbe' (2024-07-12)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/655a58a72a6601292512670343087c2d75d859c1' (2024-07-08)
  → 'github:NixOS/nixpkgs/7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9' (2024-07-12)
• Updated input 'rocksdb':
    'github:girlbossceo/rocksdb/a935c0273e1ba44eacf88ce3685a9b9831486155' (2024-07-02)
  → 'github:girlbossceo/rocksdb/911f4243e69c2e320a7a209bf1f5f3ff5f825495' (2024-07-13)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry fcb6c8a113 bump rust-rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
Charles Hall 1ab77aeb91 don't strip binary for dev builds
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 3d73b53136 update complement results
we dont implement authenticated media yet

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:35:00 +00:00
strawberry 101fdbc9b1 bump cargo.lock
Updating bytes v1.6.0 -> v1.6.1
Updating cc v1.1.0 -> v1.1.3
Updating http-body v1.0.0 -> v1.0.1
Updating syn v2.0.70 -> v2.0.71
Updating thiserror v1.0.61 -> v1.0.62
Updating thiserror-impl v1.0.61 -> v1.0.62

Signed-off-by: strawberry <strawberry@puppygock.gay>
Co-authored-by: Jason Volk <jason@zemos.net>
2024-07-14 12:35:00 +00:00
Jason Volk e53c2fbc5a fix tracing flame envfilter defaults; remove preprocessed cfgs
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 95006f7e46 fix unnecessary preprocessing cfgs
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk cce270d938 tokio metrics
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5ec49b3f62 split csp into array; integrate error; cleanup type
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk e4dc4a1ba5 fix graceful shutdown on unix socket
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk d2fb6d04c9 cleanup pending transactions before sender worker completes
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 05efd9b044 elaborate error macro and apply at various callsites
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b3f2288d07 add constant-expression string utils
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 084751ae38 refactor globals::updates into a Service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk aa7a310200 move media startup into service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 38c989a07e split service manager into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5be679e17b refactor main task stack through service mgr
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b8baa1223d reduce RoomVersionId patterns
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b87f1649d4 split error into directory
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4a6f089b23 move some config checks into check unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4600c7f32d move infallible handling into error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4cc92dd175 refactor Error::bad_config
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 93ec4e579b error macro suite
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk c111d2e395 abstract service worker pattern; restart on panic.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 03d890cd49 move admin tests into unit; fix
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk a35b6cbfdd use separate configurable jaeger envfilter
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5570220c89 use separate but configurable envfilter for sentry
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 899b79873e propagate errors from tracing init
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 57969f9480 fix large stack array exceeding limit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk d88ab37120 add configuration for sentry to send panics and errors
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk bc58e5002d add interface for current log filter; fix console suppression
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 160f48043e add selective log handle reload
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 0023b09f5b make tracing reload handles into a named map
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk db3c718ddc add config for sentry stacktraces
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk e73aa2aa21 log propagated errors in admin service; minor cleanup
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 61f2a3c68b catch panic from admin commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 0627b46f40 add panic suite to Error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk f10f5319db elaborate error log functor stack
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 16e76d45cb improve alloc stats interface; fix admin command formatting
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 2a304c2b6c add some usage stats output on the mutex maps
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 271f720286 move mutex maps out of globals into respective service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 2d251eb19c cleanup on drop for utils::mutex_map.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 01b2928d55 add make user admin command (#136)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 50c2d2b801 add command to force join user to room (#136)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 5dcdafe207 take local join branch when remote join would fail
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk c62d653989 tweak some log levels
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 454dd43d4c fix membership route ABA's
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
AlexPewMaster 8077e910f6 Fix CONDUWUIT_CONFIG variable in docker-compose.yml files (+ remove deprecated compose version) 2024-07-13 17:16:39 -04:00
strawberry b5d4a1c1b0 drop harmless state event not found message to debug_info
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry aa9540af21 slightly simplify send_state_event route
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 080975ab0e adjust/update complement conduwuit config.toml
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry f129d90900 nix: delete -DWITH_SNAPPY=1 from rocksdb cmakeFlags
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 53223a4d5f remove snappy as a default feature
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 05befa4ba2 nix: make enableLiburing in rocksdb conditional instead of default true
for some reason enableLiburing in nixpkgs rocksdb is default
true which breaks Darwin entirely

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry a8e690f22b nix: unset patches in rocksdb
we have the patch already and i dont want to make it nix
exclusive by reverting it in my rocksdb fork

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 192c1e08da add exclude_disabled and exclude_banned room list admin cmd arguments
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0fa6976d86 add client IP and user logging on join, remove unnecessary Option
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 93e7cf461d add client IP logging to media requests
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry d036d8adcb bump rust-rocksdb to 0.27.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 7a4bbe2ff6 nix: drop hardened_malloc feature from all-features builds
this is non-functional and i've spent far too long trying to figure
out what the issue is

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 438911c18d nix: remove unnecessary dependencies gathering from allFeatures
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry bd71435a22 ci: build static binaries and OCI imgs with --all-features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 53fa7c3729 nix: add all-features outputs/packages
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry d2facaee0b bump various dependencies
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0dae9280d9 nix: bump flake.lock
• Updated input 'crane':
    'github:ipetkov/crane/087e08a41009bf083d51ab35d8e30b1b7eafa7b0' (2024-07-03)
  → 'github:ipetkov/crane/0aed560c5c0a61c9385bddff471a13036203e11c' (2024-07-06)
• Updated input 'fenix':
    'github:nix-community/fenix/f6994934e25396d3a70ddb908cefccd8d3c37ac4' (2024-07-05)
  → 'github:nix-community/fenix/27cbad7cc093c5298231b87daa04db9610053651' (2024-07-06)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/cae997e3380363a906588f14c7b4587f39cf09f5' (2024-07-03)
  → 'github:rust-lang/rust-analyzer/f2afcb874e7410121c366ae601660abe327e320b' (2024-07-05)

• Updated input 'fenix':
    'github:nix-community/fenix/27cbad7cc093c5298231b87daa04db9610053651' (2024-07-06)
  → 'github:nix-community/fenix/abc0549e3560189462a7d394cc9d50af4608d103' (2024-07-08)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/f2afcb874e7410121c366ae601660abe327e320b' (2024-07-05)
  → 'github:rust-lang/rust-analyzer/a5b21ea0aa644dffd7cf958b43f11f221d53404e' (2024-07-07)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/9f4128e00b0ae8ec65918efeba59db998750ead6' (2024-07-03)
  → 'github:NixOS/nixpkgs/655a58a72a6601292512670343087c2d75d859c1' (2024-07-08)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 35336eb686 ci: use $COMPLEMENT_SRC from nix devshell for a pinned complement rev
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry efea13a675 add access control checks for room directory publishing/removing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry b5ee15a216 dont skip_all tracing instrument on fetch_state
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0873e18e14 remove random duplicate function
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 6abc4ad798 make local_aliases_for_room db iterator Send
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 373991a8d6 cleanup and fix backfill from server getting
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 68ad351f84 nix: partially remove some liburing overrides
the argument one is weird and causing build script invalid arguments

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 83e853e7a3 add "unstable" endpoint legacy Element Android/iOS seems to call
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 1c453b1b55 require authentication on change password and deactivation routes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 60141950f7 fix wrong deactivate-all argument comment
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 391bfd986e use ruma's X-Matrix to construct the sending X-Matrix header value
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry da03de1d32 bump flake.lock fully
• Updated input 'crane':
    'github:ipetkov/crane/109987da061a1bf452f435f1653c47511587d919' (2024-05-24)
  → 'github:ipetkov/crane/087e08a41009bf083d51ab35d8e30b1b7eafa7b0' (2024-07-03)
• Updated input 'fenix':
    'github:nix-community/fenix/b6fc5035b28e36a98370d0eac44f4ef3fd323df6' (2024-05-22)
  → 'github:nix-community/fenix/f6994934e25396d3a70ddb908cefccd8d3c37ac4' (2024-07-05)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/21ec8f523812b88418b2bfc64240c62b3dd967bd' (2024-05-19)
  → 'github:rust-lang/rust-analyzer/cae997e3380363a906588f14c7b4587f39cf09f5' (2024-07-03)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5710852ba686cc1fd0d3b8e22b3117d43ba374c2' (2024-05-21)
  → 'github:NixOS/nixpkgs/9f4128e00b0ae8ec65918efeba59db998750ead6' (2024-07-03)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry e54f4d4397 ci: move complement diff results test output
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry daa5c34ea3 fix empty version string for NixOS users
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 3259ea08b5 use cond compilation for config check, fix docker check w/unix sockets, use our logging instead of tracing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
Jason Volk 158de10fe6 log erroring errors; improve inspection functors.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 04:09:34 +00:00
Jason Volk 4718387dbe fix wrapper macro semicolons
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 03:38:07 +00:00
Jason Volk a43c78e55f add RumaError to Error; encapsulate RumaResponse in api
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 03:38:07 +00:00
Jason Volk 7ba0777bd3 move RumaResponse out of core Error; cleanup Error conversions.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 59d86d3641 restrict clippy::string_slice
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 51df946911 de-branch duration limit statement
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 23a9055199 relax and de-clutter let_underscore_must_use
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 100c6f572b trim unused dependencies in member crates
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk eb5556e74e additional tracing spans / log cleanup.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 8296e0ed67 slightly optimize command completer
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 8e3be6feb0 slightly optimize Destination::get_prefix()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 59c4062305 set trivial/leaf spans to debug level
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 113a27c1d5 use usize for max_request_size config item
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5af880e4f4 fix reference count on punned ColumnFamily Arc
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 56a1b0e761 restrict untripped clippies
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5722c4ae39 fix needless collect
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk dfd13780df mitigate additional cast lints
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk dcd7422c45 fix as conversions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 7397064edd fix arithmetic side-effects
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 52a561ff9e abstract expoential backoff to math utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5e72d36800 add math utils; integrate checked expression macros
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 611f09829e use shorthand constraint syntax and formatting
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk a388c2e06e allow clippy::enum_glob_use
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 24b37e03a0 add pretty time util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk a309ef55c9 restore signal state after channel failures
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
AlexPewMaster c1c084dda1 Introduce a new way of deploying conduwuit using caddy in Docker 2024-07-08 17:59:59 -04:00
strawberry 72d9e8ed2b bump conduwuit version to 0.4.5
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-07 14:32:41 -04:00
Jason Volk a3638dbb15 use rwlock for command handler.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-07 06:24:08 +00:00
Jason Volk 5254eb4f72 add basic tab completion to console
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-07 06:24:08 +00:00
Jason Volk b31e81a469 add common_prefix string util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 0e580292a6 encap admin handler init/fini in crate
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 38a24e0170 remove unused deps in member crates
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 8350aced39 add ctrl-\ quit support to admin console
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 04e3de08eb add log to error functors for Result::map_or_else
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Christoph Dittmann 2bc53139fa Don't send empty presence EDUs
I run a homeserver whose logs show a high number of incoming empty
presence EDUs originating from the user agent "Conduwuit/0.4.4". They
arrive at a rate of about 2 queries per second per Conduwuit server.

The empty EDUs all look the same, only with `origin_server_ts`
increasing:

```
{"origin":"example.com","origin_server_ts":1720266475601,"edus":[{"edu_type":"m.presence","content":{"push":[]}}]}
```

These updates are unnecessary because they don't do anything. They
only increase network traffic and CPU usage on both sides.

After this commit, the empty presence updates are no longer inserted
into the outgoing event queue.
2024-07-06 14:13:33 +02:00
strawberry 8691141237 ci: try running nix-build-and-cache prior dynamic build test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-05 02:48:36 -04:00
173 changed files with 4522 additions and 3159 deletions
+22 -22
View File
@@ -38,8 +38,11 @@ env:
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
# Get error output from nix that we can actually use
NIX_CONFIG: show-trace = true
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: |
show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
permissions:
packages: write
@@ -57,7 +60,7 @@ jobs:
uses: actions/checkout@v4
- name: Tag comparison check
if: startsWith(github.ref, 'refs/tags/v')
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
run: |
# Tag mismatch with latest repo tag check to prevent potential downgrades
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
@@ -115,7 +118,7 @@ jobs:
- name: Prepare build environment
run: |
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop .#all-features --command true
@@ -129,15 +132,10 @@ jobs:
run: |
direnv exec . engage > >(tee -a test_output.log)
- name: Sync Complement repository
uses: actions/checkout@v4
with:
repository: 'matrix-org/complement'
path: complement_src
- name: Run Complement tests
run: |
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op
direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
cp -v -f result complement_oci_image.tar.gz
- name: Upload Complement OCI image
@@ -163,11 +161,7 @@ jobs:
- name: Diff Complement results with checked-in repo results
run: |
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
tail -n 100 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_diff_output.log)
- name: Update Job Summary
if: success() || failure()
@@ -175,9 +169,15 @@ jobs:
if [ ${{ job.status }} == 'success' ]; then
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
else
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
tail -n 100 complement_diff_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
build:
@@ -240,7 +240,7 @@ jobs:
- name: Prepare build environment
run: |
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop .#all-features --command true
@@ -249,7 +249,7 @@ jobs:
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
bin/nix-build-and-cache just .#static-${{ matrix.target }}
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduit target/release/conduwuit
@@ -276,7 +276,7 @@ jobs:
- name: Build OCI image ${{ matrix.target }}
run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
- name: Upload OCI image ${{ matrix.target }}
@@ -296,15 +296,15 @@ jobs:
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
+6 -3
View File
@@ -21,8 +21,11 @@ env:
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
# Get error output from nix that we can actually use
NIX_CONFIG: show-trace = true
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: |
show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
@@ -98,7 +101,7 @@ jobs:
- name: Prepare build environment
run: |
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop --command true
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Trivy code and vulnerability scanner on repo
uses: aquasecurity/trivy-action@0.23.0
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: repo
format: sarif
@@ -34,7 +34,7 @@ jobs:
severity: CRITICAL,HIGH,MEDIUM,LOW
- name: Run Trivy code and vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.23.0
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
format: sarif
+2 -2
View File
@@ -58,7 +58,7 @@ before_script:
ci:
stage: ci
image: nixos/nix:2.23.1
image: nixos/nix:2.23.3
script:
# Cache CI dependencies
- ./bin/nix-build-and-cache ci
@@ -83,7 +83,7 @@ ci:
artifacts:
stage: artifacts
image: nixos/nix:2.23.1
image: nixos/nix:2.23.3
script:
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
- cp result/bin/conduit x86_64-unknown-linux-musl
Generated
+172 -165
View File
File diff suppressed because it is too large Load Diff
+34 -28
View File
@@ -20,11 +20,14 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/girlbossceo/conduwuit"
rust-version = "1.77.0"
version = "0.4.4"
version = "0.4.5"
[workspace.metadata.crane]
name = "conduit"
[workspace.dependencies.const-str]
version = "0.5.7"
[workspace.dependencies.sanitize-filename]
version = "0.5.0"
@@ -50,7 +53,7 @@ version = "0.8.5"
# Used for the http request / response body type for Ruma endpoints used with reqwest
[workspace.dependencies.bytes]
version = "1.6.0"
version = "1.6.1"
[workspace.dependencies.http-body-util]
version = "0.1.1"
@@ -113,7 +116,7 @@ features = [
]
[workspace.dependencies.serde]
version = "1.0.203"
version = "1.0.204"
features = ["rc"]
[workspace.dependencies.serde_json]
@@ -169,7 +172,7 @@ default-features = false
# used for conduit's CLI and admin room command parsing
[workspace.dependencies.clap]
version = "4.5.4"
version = "4.5.9"
default-features = false
features = [
"std",
@@ -197,6 +200,9 @@ features = [
"io-util",
]
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
[workspace.dependencies.libloading]
version = "0.8.3"
@@ -245,7 +251,7 @@ default-features = false
# Used for conduit::Error type
[workspace.dependencies.thiserror]
version = "1.0.61"
version = "1.0.62"
# Used when hashing the state
[workspace.dependencies.ring]
@@ -265,7 +271,7 @@ version = "2.1.1"
version = "0.3.1"
[workspace.dependencies.async-trait]
version = "0.1.80"
version = "0.1.81"
[workspace.dependencies.lru-cache]
version = "0.1.2"
@@ -303,7 +309,7 @@ features = [
[workspace.dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruwuma"
rev = "fd686e77950680462377c9105dfb4136dd49c7a0"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
[workspace.dependencies.rust-rocksdb]
path = "deps/rust-rocksdb"
@@ -311,7 +317,6 @@ package = "rust-rocksdb-uwu"
features = [
"multi-threaded-cf",
"mt_static",
"snappy",
"lz4",
"zstd",
"zlib",
@@ -380,10 +385,6 @@ version = "0.5.4"
default-features = false
features = ["use_std"]
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
default-features = false
[workspace.dependencies.console-subscriber]
version = "0.3"
@@ -405,11 +406,15 @@ features = [
[workspace.dependencies.rustyline-async]
version = "0.4.2"
default-features = false
[workspace.dependencies.termimad]
version = "0.29.4"
default-features = false
[workspace.dependencies.checked_ops]
version = "0.1"
#
# Patches
@@ -420,16 +425,16 @@ default-features = false
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://github.com/girlbossceo/tracing"
rev = "b348dca742af641c47bc390261f60711c2af573c"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing]
git = "https://github.com/girlbossceo/tracing"
rev = "b348dca742af641c47bc390261f60711c2af573c"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing-core]
git = "https://github.com/girlbossceo/tracing"
rev = "b348dca742af641c47bc390261f60711c2af573c"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing-log]
git = "https://github.com/girlbossceo/tracing"
rev = "b348dca742af641c47bc390261f60711c2af573c"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
# fixes hyper graceful shutdowns [https://github.com/programatik29/axum-server/issues/114]
# https://github.com/girlbossceo/axum-server/commit/8e3368d899079818934e61cc9c839abcbbcada8a
@@ -437,6 +442,12 @@ rev = "b348dca742af641c47bc390261f60711c2af573c"
git = "https://github.com/girlbossceo/axum-server"
rev = "8e3368d899079818934e61cc9c839abcbbcada8a"
# adds a tab completion callback: https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\: https://github.com/girlbossceo/rustyline-async/commit/67d8c49aeac03a5ef4e818f663eaa94dd7bf339b
[patch.crates-io.rustyline-async]
git = "https://github.com/girlbossceo/rustyline-async"
rev = "de26100b0db03e419a3d8e1dd26895d170d1fe50"
#
# Our crates
#
@@ -726,7 +737,6 @@ nursery = "warn"
## some sadness
missing_const_for_fn = { level = "allow", priority = 1 } # TODO
needless_collect = { level = "allow", priority = 1 } # TODO
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
@@ -736,21 +746,14 @@ significant_drop_tightening = { level = "allow", priority = 1 } # TODO
pedantic = "warn"
## some sadness
cast_possible_truncation = { level = "allow", priority = 1 }
cast_precision_loss = { level = "allow", priority = 1 }
cast_sign_loss = { level = "allow", priority = 1 }
doc_markdown = { level = "allow", priority = 1 }
error_impl_error = { level = "allow", priority = 1 }
expect_used = { level = "allow", priority = 1 }
enum_glob_use = { level = "allow", priority = 1 }
if_not_else = { level = "allow", priority = 1 }
if_then_some_else_none = { level = "allow", priority = 1 }
implicit_return = { level = "allow", priority = 1 }
inline_always = { level = "allow", priority = 1 }
map_err_ignore = { 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 }
mod_module_files = { level = "allow", priority = 1 }
module_name_repetitions = { level = "allow", priority = 1 }
no_effect_underscore_binding = { level = "allow", priority = 1 }
similar_names = { level = "allow", priority = 1 }
@@ -764,8 +767,10 @@ perf = "warn"
###################
#restriction = "warn"
#arithmetic_side_effects = "warn" # TODO
#as_conversions = "warn" # TODO
allow_attributes = "warn"
arithmetic_side_effects = "warn"
as_conversions = "warn"
as_underscore = "warn"
assertions_on_result_states = "warn"
dbg_macro = "warn"
default_union_representation = "warn"
@@ -779,7 +784,6 @@ fn_to_numeric_cast_any = "warn"
format_push_string = "warn"
get_unwrap = "warn"
impl_trait_in_params = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
lossy_float_literal = "warn"
mem_forget = "warn"
@@ -793,6 +797,7 @@ rest_pat_in_fully_bound_structs = "warn"
semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
@@ -803,6 +808,7 @@ unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unseparated_literal_suffix = "warn"
#unwrap_used = "warn" # TODO
verbose_file_reads = "warn"
###################
+12 -5
View File
@@ -7,7 +7,7 @@ set -euo pipefail
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
# points to a store path containing the Complement source code. It's likely you
# want to just pass that as the first argument to use it here.
COMPLEMENT_SRC="$1"
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
# A `.jsonl` file to write test logs to
LOG_FILE="$2"
@@ -17,12 +17,19 @@ RESULTS_FILE="$3"
OCI_IMAGE="complement-conduit:main"
# Complement tests that are skipped due to flakiness/reliability issues (likely
# Complement itself induced based on various open issues)
#
# According to Go docs, these are separated by forward slashes and not pipes (why)
# Complement tests that are skipped due to flakiness/reliability issues
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
# $COMPLEMENT_SRC needs to be a directory to Complement source code
if [ -f "$COMPLEMENT_SRC" ]; then
echo "\$COMPLEMENT_SRC must be a directory/path to Complement source code"
exit 1
fi
# quick test to make sure we can actually write to $LOG_FILE and $RESULTS_FILE
touch $LOG_FILE && rm -v $LOG_FILE
touch $RESULTS_FILE && rm -v $RESULTS_FILE
toplevel="$(git rev-parse --show-toplevel)"
pushd "$toplevel" > /dev/null
+14 -1
View File
@@ -57,6 +57,16 @@
# Defaults to 0.15
#sentry_traces_sample_rate = 0.15
# Whether to attach a stacktrace to Sentry reports.
#sentry_attach_stacktrace = false
# Send panics to sentry. This is true by default, but sentry has to be enabled.
#sentry_send_panic = true
# Send errors to sentry. This is true by default, but sentry has to be enabled. This option is
# only effective in release-mode; forced to false in debug-mode.
#sentry_send_error = true
### Database configuration
@@ -411,8 +421,11 @@ allow_profile_lookup_federation_requests = true
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
# May be useful if you have significant memory to spare to increase performance.
#
# This was previously called `conduit_cache_capacity_modifier`
#
# Defaults to 1.0.
#conduit_cache_capacity_modifier = 1.0
#cache_capacity_modifier = 1.0
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
# May be useful if you have significant memory to spare to increase performance.
+2 -2
View File
@@ -10,7 +10,7 @@ repository.workspace = true
version = "0.0.1"
[features]
default = ["snappy", "lz4", "zstd", "zlib", "bzip2"]
default = ["lz4", "zstd", "zlib", "bzip2"]
jemalloc = ["rust-rocksdb/jemalloc"]
io-uring = ["rust-rocksdb/io-uring"]
valgrind = ["rust-rocksdb/valgrind"]
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
rev = "b4887edfb84771336930855727390edec07d63fa"
rev = "4056a3b0f823013fec49f6d0b3e5698856e6476a"
#branch = "master"
default-features = false
@@ -1,5 +1,4 @@
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -24,7 +23,7 @@ services:
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
@@ -1,5 +1,4 @@
# conduwuit - Traefik Reverse Proxy Labels
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -0,0 +1,54 @@
services:
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for conduwuit!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
labels:
caddy: example.com
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
homeserver:
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
### then you are ready to go.
image: girlbossceo/conduwuit:latest
restart: unless-stopped
volumes:
- db:/var/lib/conduwuit
#- ./conduwuit.toml:/etc/conduwuit.toml
environment:
CONDUWUIT_SERVER_NAME: example.com # EDIT THIS
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
CONDUWUIT_DATABASE_BACKEND: rocksdb
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
networks:
- caddy
labels:
caddy: matrix.example.com
caddy.reverse_proxy: "{{upstreams 6167}}"
volumes:
db:
networks:
caddy:
external: true
@@ -1,5 +1,4 @@
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -16,7 +15,7 @@ services:
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
CONDUWUIT_ALLOW_REGISTRATION : 'true'
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
### Uncomment and change values as desired
# CONDUWUIT_ADDRESS: 0.0.0.0
# CONDUWUIT_PORT: 6167
@@ -28,7 +27,6 @@ services:
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
# CONDUWUIT_WORKERS: 10
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
+1 -3
View File
@@ -1,5 +1,4 @@
# conduwuit
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -24,8 +23,7 @@ services:
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
#
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
+10 -1
View File
@@ -59,13 +59,22 @@ If the `docker run` command is not for you or your setup, you can also use one o
Depending on your proxy setup, you can use one of the following files;
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
- If you don't have a `traefik` instance set up (or any other reverse proxy), use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
- If you don't have a `traefik` instance set up and would like to use it, use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
- If you want a setup that works out of the box with `caddy-docker-proxy`, use [`docker-compose.with-caddy.yml`](docker-compose.with-caddy.yml) and replace all `example.com` placeholders with your own domain
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
for your server.
When picking the `caddy-docker-proxy` compose file, it's important to first create the `caddy` network before spinning up the containers:
```bash
docker network create caddy
```
After that, you can rename it so it matches `docker-compose.yml` and spin up the containers!
Additional info about deploying conduwuit can be found [here](generic.md).
### Build
+3 -5
View File
@@ -23,7 +23,7 @@ Otherwise, follow standard Rust project build guides (installing git and cloning
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
you to make sure that the file permissions are correctly set up.
In Debian or RHEL, you can use this command to create a conduwuit user:
In Debian or Fedora/RHEL, you can use this command to create a conduwuit user:
```bash
sudo adduser --system conduwuit --group --disabled-login --no-create-home
@@ -53,13 +53,11 @@ RocksDB is the only supported database backend.
## Setting the correct file permissions
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this command on
Debian or RHEL:
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this:
```bash
sudo chown -R root:root /etc/conduwuit
sudo chmod 755 /etc/conduwuit
sudo chmod -R 755 /etc/conduwuit
```
If you use the default database path you also need to run this:
+6 -1
View File
@@ -184,5 +184,10 @@ cargo test \
name = "nix-default"
group = "tests"
script = """
nix run .#default -- --help
env DIRENV_DEVSHELL=dynamic \
direnv exec . \
bin/nix-build-and-cache just .#default
env DIRENV_DEVSHELL=dynamic \
direnv exec . \
nix run -L .#default -- --help
"""
Generated
+25 -25
View File
@@ -9,11 +9,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1717279440,
"narHash": "sha256-kH04ReTjxOpQumgWnqy40vvQLSnLGxWP6RF3nq5Esrk=",
"lastModified": 1720542474,
"narHash": "sha256-aKjJ/4l2I9+wNGTaOGRsuS3M1+IoTibqgEMPDikXm04=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "717cc95983cdc357bc347d70be20ced21f935843",
"rev": "6139576a3ce6bb992e0f6c3022528ec233e45f00",
"type": "github"
},
"original": {
@@ -81,11 +81,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1719903368,
"narHash": "sha256-PPzgxM4Bir+Zh9FUV/v+RBxEYeJxYVmi/BYo3uqt268=",
"lastModified": 1720637557,
"narHash": "sha256-oZz6nCmFmdJZpC+K1iOG2KkzTI6rlAmndxANPDVU7X0=",
"owner": "matrix-org",
"repo": "complement",
"rev": "bc97f1ddc1cd7485faf80c8935ee2641f3e1b57c",
"rev": "0d14432e010482ea9e13a6f7c47c1533c0c9d62f",
"type": "github"
},
"original": {
@@ -123,11 +123,11 @@
]
},
"locked": {
"lastModified": 1716569590,
"narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",
"lastModified": 1720546058,
"narHash": "sha256-iU2yVaPIZm5vMGdlT0+57vdB/aPq/V5oZFBRwYw+HBM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "109987da061a1bf452f435f1653c47511587d919",
"rev": "2d83156f23c43598cf44e152c33a59d3892f8b29",
"type": "github"
},
"original": {
@@ -209,11 +209,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1716359173,
"narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
"lastModified": 1720852044,
"narHash": "sha256-3NBYz8VuXuKU+8ONd9NFafCNjPEGHIZQ2Mdoam1a4mY=",
"owner": "nix-community",
"repo": "fenix",
"rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
"rev": "5087b12a595ee73131a944d922f24d81dae05725",
"type": "github"
},
"original": {
@@ -381,11 +381,11 @@
"liburing": {
"flake": false,
"locked": {
"lastModified": 1719025212,
"narHash": "sha256-kD0yhjNStqC6uFqC1AxBwUpc/HlSFtiKrV+gwDyroDc=",
"lastModified": 1720798442,
"narHash": "sha256-gtPppAoksMLW4GuruQ36nf4EAqIA1Bs6V9Xcx8dBxrQ=",
"owner": "axboe",
"repo": "liburing",
"rev": "7b3245583069bd481190c9da18f22e9fc8c3a805",
"rev": "1d674f83b7d0f07553ac44d99a401b05853d9dbe",
"type": "github"
},
"original": {
@@ -606,11 +606,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1716330097,
"narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=",
"lastModified": 1720768451,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
"type": "github"
},
"original": {
@@ -673,16 +673,16 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1719949653,
"narHash": "sha256-DYx7XHH2GEh17GukKhXs6laM6l+eugCmRkF0adpi9wk=",
"lastModified": 1720900786,
"narHash": "sha256-Vta9Um/RRuWwZ46BjXftV06iWLm/j/9MX39emXUvSAY=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "a935c0273e1ba44eacf88ce3685a9b9831486155",
"rev": "911f4243e69c2e320a7a209bf1f5f3ff5f825495",
"type": "github"
},
"original": {
"owner": "girlbossceo",
"ref": "v9.3.1",
"ref": "v9.4.0",
"repo": "rocksdb",
"type": "github"
}
@@ -705,11 +705,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1716107283,
"narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
"lastModified": 1720717809,
"narHash": "sha256-6I+fm+nTLF/iaj7ffiFGlSY7POmubwUaPA/Wq0Bm53M=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
"rev": "ffbc5ad993d5cd2f3b8bcf9a511165470944ab91",
"type": "github"
},
"original": {
+91 -13
View File
@@ -9,8 +9,7 @@
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.3.1"; flake = false; };
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.4.0"; flake = false; };
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
};
@@ -42,6 +41,37 @@
"v"
(builtins.fromJSON (builtins.readFile ./flake.lock))
.nodes.rocksdb.original.ref;
# we have this already at https://github.com/girlbossceo/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# unsetting this so i don't have to revert it and make this nix exclusive
patches = [];
cmakeFlags = pkgs.lib.subtractLists
[
# no real reason to have snappy, no one uses this
"-DWITH_SNAPPY=1"
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=1"
# we dont need to build rocksdb tests
"-DWITH_TESTS=1"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=1"
]
old.cmakeFlags
++ [
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# we dont need trace tools
"-DWITH_TRACE_TOOLS=0"
# we dont need to build rocksdb tests
"-DWITH_TESTS=0"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=0"
];
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
liburing = pkgs.liburing.overrideAttrs (old: {
@@ -50,16 +80,6 @@
configureFlags = pkgs.lib.subtractLists
[ "--enable-static" "--disable-shared" ]
old.configureFlags;
postInstall = old.postInstall + ''
# we remove the extra outputs
#
# we need to do this to prevent rocksdb from trying to link the
# static library in a dynamic stdenv
rm $out/lib/liburing*${
if pkgs.stdenv.hostPlatform.isStatic then ".so*" else ".a"
}
'';
});
});
@@ -124,9 +144,29 @@
{
packages = {
default = scopeHost.main;
all-features = scopeHost.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
oci-image = scopeHost.oci-image;
oci-image-all-features = scopeHost.oci-image.override {
main = scopeHost.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
};
oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["hardened_malloc"];
@@ -161,6 +201,20 @@
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with `--all-features`
{
name = "${binaryName}-all-features";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
@@ -175,6 +229,22 @@
value = scopeCrossStatic.oci-image;
}
# An output for an OCI image based on that binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
};
}
# An output for an OCI image based on that binary with hardened_malloc
{
name = "oci-image-${crossSystem}-hmalloc";
@@ -196,7 +266,15 @@
devShells.default = mkDevShell scopeHostStatic;
devShells.all-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override { all_features = true; };
main = prev.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
}));
devShells.no-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
+7 -3
View File
@@ -5,13 +5,17 @@ allow_guest_registration = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
allow_unstable_room_versions = true
database_backend = "rocksdb"
database_path = "/database"
log = "trace"
log = "trace,h2=warn,hyper=warn"
port = [8008, 8448]
trusted_servers = []
query_trusted_key_servers_first = false
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"]
media_compat_file_link = false
media_statup_check = false
rocksdb_direct_io = false
[global.tls]
certs = "/certificate.crt"
+6
View File
@@ -13,6 +13,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
lib.concatStringsSep
" "
([]
++ lib.optionals
stdenv.targetPlatform.isx86_64
[ "-C" "target-cpu=x86-64-v2" ]
++ lib.optionals
stdenv.targetPlatform.isAarch64
[ "-C" "target-cpu=cortex-a55" ] # cortex-a55 == ARMv8.2-a
# This disables PIE for static builds, which isn't great in terms
# of security. Unfortunately, my hand is forced because nixpkgs'
# `libstdc++.a` is built without `-fPIE`, which precludes us from
+34 -10
View File
@@ -25,11 +25,7 @@ let
# on the nix side depend on feature values.
crateFeatures = path:
let manifest = lib.importTOML "${path}/Cargo.toml"; in
lib.remove "default" (lib.attrNames manifest.features) ++
lib.attrNames
(lib.filterAttrs
(_: dependency: dependency.optional or false)
manifest.dependencies);
lib.remove "default" (lib.attrNames manifest.features);
crateDefaultFeatures = path:
(lib.importTOML "${path}/Cargo.toml").features.default;
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
@@ -43,7 +39,7 @@ features'' = lib.subtractLists disable_features' features';
featureEnabled = feature : builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && stdenv.isLinux;
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin;
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
@@ -70,12 +66,34 @@ buildDepsOnlyEnv =
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
enableLiburing = enableLiburing;
}).overrideAttrs (old: {
# TODO: static rocksdb fails to build on darwin
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
# TODO: switch to enableUring option once https://github.com/NixOS/nixpkgs/pull/314945 is available
buildInputs = old.buildInputs ++ lib.optional enableLiburing liburing;
enableLiburing = enableLiburing;
sse42Support = stdenv.targetPlatform.isx86_64;
cmakeFlags = if stdenv.targetPlatform.isx86_64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isx86_64 [
"-DPORTABLE=x86-64-v2"
"-DUSE_SSE=1"
"-DHAVE_SSE=1"
"-DHAVE_SSE42=1"
]
else if stdenv.targetPlatform.isAarch64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isAarch64 [
# cortex-a55 == ARMv8.2-a
"-DPORTABLE=armv8.2-a"
]
else old.cmakeFlags;
});
in
{
@@ -102,7 +120,11 @@ buildPackageEnv = {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
" -L${lib.getLib liburing}/lib -luring";
" -L${lib.getLib liburing}/lib -luring"
+ lib.optionalString stdenv.targetPlatform.isx86_64
" -Ctarget-cpu=x86-64-v2"
+ lib.optionalString stdenv.targetPlatform.isAarch64
" -Ctarget-cpu=cortex-a55"; # cortex-a55 == ARMv8.2-a
};
@@ -127,6 +149,8 @@ commonAttrs = {
];
};
dontStrip = profile == "dev";
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
nativeBuildInputs = [
+1 -4
View File
@@ -29,15 +29,12 @@ release_max_log_level = [
clap.workspace = true
conduit-api.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
const-str.workspace = true
futures-util.workspace = true
log.workspace = true
loole.workspace = true
regex.workspace = true
ruma.workspace = true
serde_json.workspace = true
serde.workspace = true
serde_yaml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
+52 -6
View File
@@ -58,7 +58,7 @@ pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent
));
}
let string = body[1..body.len() - 1].join("\n");
let string = body[1..body.len().saturating_sub(1)].join("\n");
match serde_json::from_str(&string) {
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
Ok(hash) => {
@@ -314,6 +314,8 @@ pub(super) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMe
pub(super) async fn change_log_level(
_body: Vec<&str>, filter: Option<String>, reset: bool,
) -> Result<RoomMessageEventContent> {
let handles = &["console"];
if reset {
let old_filter_layer = match EnvFilter::try_new(&services().globals.config.log) {
Ok(s) => s,
@@ -324,7 +326,12 @@ pub(super) async fn change_log_level(
},
};
match services().server.log.reload.reload(&old_filter_layer) {
match services()
.server
.log
.reload
.reload(&old_filter_layer, Some(handles))
{
Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}",
@@ -349,7 +356,12 @@ pub(super) async fn change_log_level(
},
};
match services().server.log.reload.reload(&new_filter_layer) {
match services()
.server
.log
.reload
.reload(&new_filter_layer, Some(handles))
{
Ok(()) => {
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
},
@@ -570,7 +582,7 @@ pub(super) async fn force_set_room_state_from_server(
.state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)?;
let state_lock = services().globals.roomid_mutex_state.lock(&room_id).await;
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
services()
.rooms
.state
@@ -632,12 +644,46 @@ pub(super) async fn resolve_true_destination(
pub(super) fn memory_stats() -> RoomMessageEventContent {
let html_body = conduit::alloc::memory_stats();
if html_body.is_empty() {
if html_body.is_none() {
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc.");
}
RoomMessageEventContent::text_html(
"This command's output can only be viewed by clients that render HTML.".to_owned(),
html_body,
html_body.expect("string result"),
)
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
Ok(RoomMessageEventContent::text_markdown(out))
}
#[cfg(not(tokio_unstable))]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.",
))
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_interval().map_or_else(
|| "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
Ok(RoomMessageEventContent::text_markdown(out))
}
#[cfg(not(tokio_unstable))]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.",
))
}
+9
View File
@@ -160,6 +160,13 @@ pub(super) enum DebugCommand {
/// - Print extended memory usage
MemoryStats,
/// - Print general tokio runtime metric totals.
RuntimeMetrics,
/// - Print detailed tokio runtime metrics accumulated since last command
/// invocation.
RuntimeInterval,
/// - Developer test stubs
#[command(subcommand)]
Tester(TesterCommand),
@@ -213,6 +220,8 @@ pub(super) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<Ro
no_cache,
} => resolve_true_destination(body, server_name, no_cache).await?,
DebugCommand::MemoryStats => memory_stats(),
DebugCommand::RuntimeMetrics => runtime_metrics(body).await?,
DebugCommand::RuntimeInterval => runtime_interval(body).await?,
DebugCommand::Tester(command) => tester::process(command, body).await?,
})
}
+3 -2
View File
@@ -16,8 +16,9 @@ pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Resul
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let map = services()
.globals
.roomid_federationhandletime
.rooms
.event_handler
.federation_handletime
.read()
.expect("locked");
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
+134 -64
View File
@@ -1,15 +1,19 @@
use std::time::Instant;
use std::{panic::AssertUnwindSafe, time::Instant};
use clap::Parser;
use conduit::trace;
use ruma::events::{
relation::InReplyTo,
room::message::{Relation::Reply, RoomMessageEventContent},
use clap::{CommandFactory, Parser};
use conduit::{error, trace, Error};
use futures_util::future::FutureExt;
use ruma::{
events::{
relation::InReplyTo,
room::message::{Relation::Reply, RoomMessageEventContent},
},
OwnedEventId,
};
extern crate conduit_service as service;
use conduit::Result;
use conduit::{utils::string::common_prefix, Result};
pub(crate) use service::admin::{Command, Service};
use service::admin::{CommandOutput, CommandResult, HandlerResult};
@@ -20,7 +24,6 @@ use crate::{
};
pub(crate) const PAGE_SIZE: usize = 100;
#[cfg_attr(test, derive(Debug))]
#[derive(Parser)]
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand {
@@ -62,25 +65,46 @@ pub(crate) enum AdminCommand {
}
#[must_use]
pub fn handle(command: Command) -> HandlerResult { Box::pin(handle_command(command)) }
pub(crate) fn handle(command: Command) -> HandlerResult { Box::pin(handle_command(command)) }
#[must_use]
pub(crate) fn complete(line: &str) -> String { complete_admin_command(AdminCommand::command(), line) }
#[tracing::instrument(skip_all, name = "admin")]
async fn handle_command(command: Command) -> CommandResult {
let Some(mut content) = process_admin_message(command.command).await else {
return Ok(None);
};
AssertUnwindSafe(process_command(&command))
.catch_unwind()
.await
.map_err(Error::from_panic)
.or_else(|error| handle_panic(&error, command))
}
content.relates_to = command.reply_id.map(|event_id| Reply {
async fn process_command(command: &Command) -> CommandOutput {
process_admin_message(&command.command)
.await
.and_then(|content| reply(content, command.reply_id.clone()))
}
fn handle_panic(error: &Error, command: Command) -> CommandResult {
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
let content = RoomMessageEventContent::notice_markdown(msg);
error!("Panic while processing command: {error:?}");
Ok(reply(content, command.reply_id))
}
fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -> Option<RoomMessageEventContent> {
content.relates_to = reply_id.map(|event_id| Reply {
in_reply_to: InReplyTo {
event_id,
},
});
Ok(Some(content))
Some(content)
}
// Parse and process a message from the admin room
async fn process_admin_message(msg: String) -> CommandOutput {
async fn process_admin_message(msg: &str) -> CommandOutput {
let mut lines = msg.lines().filter(|l| !l.trim().is_empty());
let command = lines.next().expect("each string has at least one line");
let body = lines.collect::<Vec<_>>();
@@ -100,59 +124,11 @@ async fn process_admin_message(msg: String) -> CommandOutput {
match result {
Ok(reply) => Some(reply),
Err(error) => Some(RoomMessageEventContent::notice_markdown(format!(
"Encountered an error while handling the command:\n```\n{error}\n```"
"Encountered an error while handling the command:\n```\n{error:#?}\n```"
))),
}
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
let mut argv = command_line.split_whitespace().collect::<Vec<_>>();
// Remove any escapes that came with a server-side escape command
if !argv.is_empty() && argv[0].ends_with("admin") {
argv[0] = argv[0].trim_start_matches('\\');
}
// First indice has to be "admin" but for console convenience we add it here
let server_user = services().globals.server_user.as_str();
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with(server_user) {
argv.insert(0, "admin");
}
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help");
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv1;
if argv.len() > 1 && argv[1].contains('_') {
command_with_dashes_argv1 = argv[1].replace('_', "-");
argv[1] = &command_with_dashes_argv1;
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv2;
if argv.len() > 2 && argv[2].contains('_') {
command_with_dashes_argv2 = argv[2].replace('_', "-");
argv[2] = &command_with_dashes_argv2;
}
// if the user is using the `query` command (argv[1]), replace the database
// function/table calls with underscores to match the codebase
let command_with_dashes_argv3;
if argv.len() > 3 && argv[1].eq("query") {
command_with_dashes_argv3 = argv[3].replace('_', "-");
argv[3] = &command_with_dashes_argv3;
}
trace!(?command_line, ?argv, "parse");
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
#[tracing::instrument(skip_all, name = "command")]
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
let reply_message_content = match command {
@@ -169,3 +145,97 @@ async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result
Ok(reply_message_content)
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
let argv = parse_command_line(command_line);
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String {
let argv = parse_command_line(line);
let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));
'token: for token in argv.into_iter().skip(1) {
let cmd_ = cmd.clone();
let mut choice = Vec::new();
for sub in cmd_.get_subcommands() {
let name = sub.get_name();
if *name == token {
// token already complete; recurse to subcommand
ret.push(token);
cmd.clone_from(sub);
continue 'token;
} else if name.starts_with(&token) {
// partial match; add to choices
choice.push(name);
}
}
if choice.len() == 1 {
// One choice. Add extra space because it's complete
let choice = *choice.first().expect("only choice");
ret.push(choice.to_owned());
ret.push(String::new());
} else if choice.is_empty() {
// Nothing found, return original string
ret.push(token);
} else {
// Find the common prefix
ret.push(common_prefix(&choice).into());
}
// Return from completion
return ret.join(" ");
}
// Return from no completion. Needs a space though.
ret.push(String::new());
ret.join(" ")
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_command_line(command_line: &str) -> Vec<String> {
let mut argv = command_line
.split_whitespace()
.map(str::to_owned)
.collect::<Vec<String>>();
// Remove any escapes that came with a server-side escape command
if !argv.is_empty() && argv[0].ends_with("admin") {
argv[0] = argv[0].trim_start_matches('\\').into();
}
// First indice has to be "admin" but for console convenience we add it here
let server_user = services().globals.server_user.as_str();
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with(server_user) {
argv.insert(0, "admin".to_owned());
}
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help".to_owned());
}
// Backwards compatibility with `register_appservice`-style commands
if argv.len() > 1 && argv[1].contains('_') {
argv[1] = argv[1].replace('_', "-");
}
// Backwards compatibility with `register_appservice`-style commands
if argv.len() > 2 && argv[2].contains('_') {
argv[2] = argv[2].replace('_', "-");
}
// if the user is using the `query` command (argv[1]), replace the database
// function/table calls with underscores to match the codebase
if argv.len() > 3 && argv[1].eq("query") {
argv[3] = argv[3].replace('_', "-");
}
trace!(?command_line, ?argv, "parse");
argv
}
+26 -26
View File
@@ -9,6 +9,7 @@ pub(crate) mod media;
pub(crate) mod query;
pub(crate) mod room;
pub(crate) mod server;
mod tests;
pub(crate) mod user;
pub(crate) mod utils;
@@ -17,7 +18,6 @@ extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
pub use handler::handle;
pub(crate) use service::{services, user_is_local};
pub(crate) use crate::{
@@ -28,29 +28,29 @@ pub(crate) use crate::{
mod_ctor! {}
mod_dtor! {}
#[cfg(test)]
mod test {
use clap::Parser;
use crate::handler::AdminCommand;
#[test]
fn get_help_short() { get_help_inner("-h"); }
#[test]
fn get_help_long() { get_help_inner("--help"); }
#[test]
fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) {
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
.unwrap_err()
.to_string();
// Search for a handful of keywords that suggest the help printed properly
assert!(error.contains("Usage:"));
assert!(error.contains("Commands:"));
assert!(error.contains("Options:"));
}
/// Install the admin command handler
pub async fn init() {
_ = services()
.admin
.complete
.write()
.expect("locked for writing")
.insert(handler::complete);
_ = services()
.admin
.handle
.write()
.await
.insert(handler::handle);
}
/// Uninstall the admin command handler
pub async fn fini() {
_ = services().admin.handle.write().await.take();
_ = services()
.admin
.complete
.write()
.expect("locked for writing")
.take();
}
+1 -1
View File
@@ -26,7 +26,7 @@ pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
},
Globals::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now();
let results = services().globals.db.last_check_for_updates_id();
let results = services().updates.last_check_for_updates_id();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
+11 -1
View File
@@ -16,6 +16,14 @@ pub(super) enum RoomCommand {
/// - List all rooms the server knows about
List {
page: Option<usize>,
/// Excludes rooms that we have federation disabled with
#[arg(long)]
exclude_disabled: bool,
/// Excludes rooms that we have banned
#[arg(long)]
exclude_banned: bool,
},
#[command(subcommand)]
@@ -179,6 +187,8 @@ pub(super) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<Roo
RoomCommand::List {
page,
} => list(body, page).await?,
exclude_disabled,
exclude_banned,
} => list(body, page, exclude_disabled, exclude_banned).await?,
})
}
+32 -4
View File
@@ -1,18 +1,46 @@
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
use ruma::events::room::message::RoomMessageEventContent;
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(super) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
pub(super) async fn list(
_body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
) -> Result<RoomMessageEventContent> {
// TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1);
let mut rooms = services()
.rooms
.metadata
.iter_ids()
.filter_map(Result::ok)
.map(|id: OwnedRoomId| get_room_info(&id))
.filter_map(|room_id| {
room_id
.ok()
.filter(|room_id| {
if exclude_disabled
&& services()
.rooms
.metadata
.is_disabled(room_id)
.unwrap_or(false)
{
return false;
}
if exclude_banned
&& services()
.rooms
.metadata
.is_banned(room_id)
.unwrap_or(false)
{
return false;
}
true
})
.map(|room_id| get_room_info(&room_id))
})
.collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1);
rooms.reverse();
+9 -16
View File
@@ -1,7 +1,5 @@
use api::client::leave_room;
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId,
};
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use tracing::{debug, error, info, warn};
use super::{super::Service, RoomModerationCommand};
@@ -124,9 +122,7 @@ async fn ban_room(
.is_admin(local_user)
.unwrap_or(true))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!(
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, &room_id
@@ -153,9 +149,7 @@ async fn ban_room(
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await {
error!(
@@ -191,7 +185,10 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
));
}
let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let rooms_s = body
.clone()
.drain(1..body.len().saturating_sub(1))
.collect::<Vec<_>>();
let admin_room_alias = &services().globals.admin_alias;
@@ -332,9 +329,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.is_admin(local_user)
.unwrap_or(true))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!(
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, room_id
@@ -361,9 +356,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await {
error!(
+13 -26
View File
@@ -1,24 +1,17 @@
use conduit::{warn, Error, Result};
use conduit::{utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent;
use crate::services;
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let seconds = services()
let elapsed = services()
.server
.started
.elapsed()
.expect("standard duration")
.as_secs();
let result = format!(
"up {} days, {} hours, {} minutes, {} seconds.",
seconds / 86400,
(seconds % 86400) / 60 / 60,
(seconds % 3600) / 60,
seconds % 60,
);
.expect("standard duration");
Ok(RoomMessageEventContent::notice_plain(result))
let result = time::pretty(elapsed);
Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
}
pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
@@ -27,17 +20,12 @@ pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventCont
}
pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let response0 = services().memory_usage().await?;
let response1 = services().db.db.memory_usage()?;
let response2 = conduit::alloc::memory_usage();
let services_usage = services().memory_usage().await?;
let database_usage = services().db.db.memory_usage()?;
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
Ok(RoomMessageEventContent::text_plain(format!(
"Services:\n{response0}\nDatabase:\n{response1}\n{}",
if !response2.is_empty() {
format!("Allocator:\n {response2}")
} else {
String::new()
}
"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
)))
}
@@ -100,11 +88,10 @@ pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessage
use conduit::utils::sys::current_exe_deleted;
if !force && current_exe_deleted() {
return Err(Error::Err(
"The server cannot be restarted because the executable was tampered with. If this is expected use --force \
to override."
.to_owned(),
));
return Err!(
"The server cannot be restarted because the executable changed. If this is expected use --force to \
override."
);
}
services().server.restart()?;
+26
View File
@@ -0,0 +1,26 @@
#![cfg(test)]
#[test]
fn get_help_short() { get_help_inner("-h"); }
#[test]
fn get_help_long() { get_help_inner("--help"); }
#[test]
fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) {
use clap::Parser;
use crate::handler::AdminCommand;
let Err(error) = AdminCommand::try_parse_from(["argv[0] doesn't matter", input]) else {
panic!("no error!");
};
let error = error.to_string();
// Search for a handful of keywords that suggest the help printed properly
assert!(error.contains("Usage:"));
assert!(error.contains("Commands:"));
assert!(error.contains("Options:"));
}
+36 -4
View File
@@ -8,7 +8,7 @@ use ruma::{
tag::{TagEvent, TagEventContent, TagInfo},
RoomAccountDataEventType,
},
OwnedRoomId, OwnedUserId, RoomId,
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
};
use tracing::{error, info, warn};
@@ -23,7 +23,7 @@ pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
match services().users.list_local_users() {
Ok(users) => {
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
plain_msg += &users.join("\n");
plain_msg += users.join("\n").as_str();
plain_msg += "\n```";
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
@@ -95,7 +95,7 @@ pub(super) async fn create(
if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper(
Some(&user_id),
&user_id,
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
@@ -195,7 +195,10 @@ pub(super) async fn deactivate_all(
));
}
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let usernames = body
.clone()
.drain(1..body.len().saturating_sub(1))
.collect::<Vec<_>>();
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
let mut admins = Vec::new();
@@ -331,6 +334,35 @@ pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
}
pub(super) async fn force_join_room(
_body: Vec<&str>, user_id: String, room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?;
let room_id = services().rooms.alias.resolve(&room_id).await?;
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
join_room_by_id_helper(&user_id, &room_id, None, &[], None).await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.",
)))
}
pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?;
let displayname = services()
.users
.displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string());
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
service::admin::make_user_admin(&user_id, displayname).await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been granted admin privileges.",
)))
}
pub(super) async fn put_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> {
+20 -2
View File
@@ -2,7 +2,7 @@ mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId, RoomId};
use self::commands::*;
@@ -49,7 +49,7 @@ pub(super) enum UserCommand {
/// Markdown code block below the command.
DeactivateAll {
#[arg(short, long)]
/// Remove users from their joined rooms
/// Does not leave any rooms the user is in on deactivation
no_leave_rooms: bool,
#[arg(short, long)]
/// Also deactivate admin accounts and will assume leave all rooms too
@@ -65,6 +65,17 @@ pub(super) enum UserCommand {
user_id: String,
},
/// - Manually join a local user to a room.
ForceJoinRoom {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Grant server-admin privileges to a user.
MakeUserAdmin {
user_id: String,
},
/// - Puts a room tag for the specified user and room ID.
///
/// This is primarily useful if you'd like to set your admin room
@@ -113,6 +124,13 @@ pub(super) async fn process(command: UserCommand, body: Vec<&str>) -> Result<Roo
UserCommand::ListJoinedRooms {
user_id,
} => list_joined_rooms(body, user_id).await?,
UserCommand::ForceJoinRoom {
user_id,
room_id,
} => force_join_room(body, user_id, room_id).await?,
UserCommand::MakeUserAdmin {
user_id,
} => make_user_admin(body, user_id).await?,
UserCommand::PutRoomTag {
user_id,
room_id,
+5 -5
View File
@@ -1,4 +1,4 @@
use conduit_core::Error;
use conduit_core::{err, Err};
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
use service::user_is_local;
@@ -33,7 +33,7 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
/// Parses user ID
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
.map_err(|e| err!("The supplied username is not a valid username: {e}"))
}
/// Parses user ID as our local user
@@ -41,7 +41,7 @@ pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_user_id(user_id)?;
if !user_is_local(&user_id) {
return Err(Error::Err(String::from("User does not belong to our server.")));
return Err!("User {user_id:?} does not belong to our server.");
}
Ok(user_id)
@@ -52,11 +52,11 @@ pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_local_user_id(user_id)?;
if !services().users.exists(&user_id)? {
return Err(Error::Err(String::from("User does not exist on this server.")));
return Err!("User {user_id:?} does not exist on this server.");
}
if services().users.is_deactivated(&user_id)? {
return Err(Error::Err(String::from("User is deactivated.")));
return Err!("User {user_id:?} is deactivated.");
}
Ok(user_id)
+2 -1
View File
@@ -41,9 +41,11 @@ bytes.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
const-str.workspace = true
futures-util.workspace = true
hmac.workspace = true
http.workspace = true
http-body-util.workspace = true
hyper.workspace = true
image.workspace = true
ipaddress.workspace = true
@@ -56,7 +58,6 @@ serde_html_form.workspace = true
serde_json.workspace = true
serde.workspace = true
sha-1.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
webpage.workspace = true
+14 -4
View File
@@ -309,7 +309,7 @@ pub(crate) async fn register_route(
// log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
info!("New guest user \"{user_id}\" registered on this server from IP.");
info!("New guest user \"{user_id}\" registered on this server.");
if let Some(device_display_name) = &body.initial_device_display_name {
if body
@@ -376,7 +376,7 @@ pub(crate) async fn register_route(
if let Some(room_id_server_name) = room.server_name() {
if let Err(e) = join_room_by_id_helper(
Some(&user_id),
&user_id,
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
@@ -423,7 +423,12 @@ pub(crate) async fn register_route(
pub(crate) async fn change_password_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
@@ -512,7 +517,12 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
pub(crate) async fn deactivate_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
+44 -8
View File
@@ -1,4 +1,5 @@
use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Error, Result};
use ruma::{
api::{
client::{
@@ -10,14 +11,16 @@ use ruma::{
},
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork},
events::{
room::join_rules::{JoinRule, RoomJoinRulesEventContent},
room::{
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
StateEventType,
},
uint, ServerName, UInt,
uint, RoomId, ServerName, UInt, UserId,
};
use tracing::{error, info, warn};
use crate::{service::server_is_ours, services, Error, Result, Ruma};
use crate::{service::server_is_ours, services, Ruma};
/// # `POST /_matrix/client/v3/publicRooms`
///
@@ -103,8 +106,6 @@ pub(crate) async fn get_public_rooms_route(
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
///
/// Sets the visibility of a given room in the room directory.
///
/// - TODO: Access control checks
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
pub(crate) async fn set_room_visibility_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
@@ -116,6 +117,13 @@ pub(crate) async fn set_room_visibility_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
if !user_can_publish_room(sender_user, &body.room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
));
}
match &body.visibility {
room::Visibility::Public => {
if services().globals.config.lockdown_public_room_directory && !services().users.is_admin(sender_user)? {
@@ -268,8 +276,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
_ => None,
})
.map_err(|e| {
error!("Invalid room join rule event in database: {}", e);
Error::BadDatabase("Invalid room join rule event in database.")
err!(Database(error!("Invalid room join rule event in database: {e}")))
})
})
.transpose()?
@@ -351,3 +358,32 @@ pub(crate) async fn get_public_rooms_filtered_helper(
total_room_count_estimate: Some(total_room_count_estimate),
})
}
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
fn user_can_publish_room(user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) =
services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
{
serde_json::from_str(event.content.get())
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
})
} else if let Some(event) =
services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
{
Ok(event.sender == user_id)
} else {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
));
}
}
+8 -13
View File
@@ -1,9 +1,9 @@
use std::{
cmp,
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::{Duration, Instant},
time::Instant,
};
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
@@ -18,15 +18,11 @@ use ruma::{
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
};
use serde_json::json;
use service::user_is_local;
use tracing::debug;
use super::SESSION_ID_LENGTH;
use crate::{
service::user_is_local,
services,
utils::{self},
Error, Result, Ruma,
};
use crate::{services, Ruma};
/// # `POST /_matrix/client/r0/keys/upload`
///
@@ -357,11 +353,10 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
.get(server)
{
// Exponential backoff
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off query from {:?}", server);
const MIN: u64 = 5 * 60;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off query from {server:?}");
return (server, Err(Error::BadServerResponse("bad query, still backing off")));
}
}
+51 -22
View File
@@ -2,6 +2,8 @@
use std::{io::Cursor, sync::Arc, time::Duration};
use axum_client_ip::InsecureClientIp;
use conduit::{debug, error, utils::math::ruma_from_usize, warn};
use image::io::Reader as ImgReader;
use ipaddress::IPAddress;
use reqwest::Url;
@@ -12,7 +14,6 @@ use ruma::api::client::{
get_media_preview,
},
};
use tracing::{debug, error, warn};
use webpage::HTML;
use crate::{
@@ -44,7 +45,7 @@ pub(crate) async fn get_media_config_route(
_body: Ruma<get_media_config::v3::Request>,
) -> Result<get_media_config::v3::Response> {
Ok(get_media_config::v3::Response {
upload_size: services().globals.max_request_size().into(),
upload_size: ruma_from_usize(services().globals.config.max_request_size),
})
}
@@ -64,18 +65,22 @@ pub(crate) async fn get_media_config_v1_route(
/// # `GET /_matrix/media/v3/preview_url`
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_route(
body: Ruma<get_media_preview::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url;
if !url_preview_allowed(url) {
warn!(%sender_user, "URL is not allowed to be previewed: {url}");
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed"));
}
match get_url_preview(url).await {
Ok(preview) => {
let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
error!("Failed to convert UrlPreviewData into a serde json value: {}", e);
error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
Error::BadRequest(
ErrorKind::LimitExceeded {
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
@@ -87,7 +92,7 @@ pub(crate) async fn get_media_preview_route(
Ok(get_media_preview::v3::Response::from_raw_value(res))
},
Err(e) => {
warn!("Failed to generate a URL preview: {e}");
warn!(%sender_user, "Failed to generate a URL preview: {e}");
// there doesn't seem to be an agreed-upon error code in the spec.
// the only response codes in the preview_url spec page are 200 and 429.
@@ -108,10 +113,13 @@ pub(crate) async fn get_media_preview_route(
/// See <https://spec.matrix.org/legacy/legacy/#id27>
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_v1_route(
body: Ruma<get_media_preview::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>,
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
get_media_preview_route(body).await.map(RumaResponse)
get_media_preview_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `POST /_matrix/media/v3/upload`
@@ -120,8 +128,9 @@ pub(crate) async fn get_media_preview_v1_route(
///
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_route(
body: Ruma<create_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -167,10 +176,13 @@ pub(crate) async fn create_content_route(
///
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_v1_route(
body: Ruma<create_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> {
create_content_route(body).await.map(RumaResponse)
create_content_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}`
@@ -181,16 +193,20 @@ pub(crate) async fn create_content_v1_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> Result<get_content::v3::Response> {
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>,
) -> Result<get_content::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services().media.get(&mxc).await?
{
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
let file = content.expect("content");
Ok(get_content::v3::Response {
file,
@@ -243,10 +259,13 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_v1_route(
body: Ruma<get_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>,
) -> Result<RumaResponse<get_content::v3::Response>> {
get_content_route(body).await.map(RumaResponse)
get_content_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
@@ -257,14 +276,15 @@ pub(crate) async fn get_content_v1_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_as_filename_route(
body: Ruma<get_content_as_filename::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<get_content_as_filename::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services().media.get(&mxc).await?
{
@@ -274,6 +294,7 @@ pub(crate) async fn get_content_as_filename_route(
Some(body.filename.clone()),
));
let file = content.expect("content");
Ok(get_content_as_filename::v3::Response {
file,
content_type,
@@ -328,10 +349,13 @@ pub(crate) async fn get_content_as_filename_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_as_filename_v1_route(
body: Ruma<get_content_as_filename::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
get_content_as_filename_route(body).await.map(RumaResponse)
get_content_as_filename_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
@@ -342,14 +366,15 @@ pub(crate) async fn get_content_as_filename_v1_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
pub(crate) async fn get_content_thumbnail_route(
body: Ruma<get_content_thumbnail::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<get_content_thumbnail::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services()
.media
@@ -365,6 +390,7 @@ pub(crate) async fn get_content_thumbnail_route(
.await?
{
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
let file = content.expect("content");
Ok(get_content_thumbnail::v3::Response {
file,
@@ -453,10 +479,13 @@ pub(crate) async fn get_content_thumbnail_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
pub(crate) async fn get_content_thumbnail_v1_route(
body: Ruma<get_content_thumbnail::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
get_content_thumbnail_route(body).await.map(RumaResponse)
get_content_thumbnail_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
async fn get_remote_content(
+45 -64
View File
@@ -1,13 +1,15 @@
use std::{
cmp,
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
net::IpAddr,
sync::Arc,
time::{Duration, Instant},
time::Instant,
};
use axum_client_ip::InsecureClientIp;
use conduit::utils::mutex_map;
use conduit::{
debug, debug_warn, error, info, trace, utils, utils::math::continue_exponential_backoff_secs, warn, Error,
PduEvent, Result,
};
use ruma::{
api::{
client::{
@@ -33,17 +35,17 @@ use ruma::{
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use service::sending::convert_to_outgoing_federation_event;
use tokio::sync::RwLock;
use tracing::{debug, error, info, trace, warn};
use crate::{
client::{update_avatar_url, update_displayname},
service::{
pdu::{gen_event_id_canonical_json, PduBuilder},
rooms::state::RoomMutexGuard,
sending::convert_to_outgoing_federation_event,
server_is_ours, user_is_local,
},
services, utils, Error, PduEvent, Result, Ruma,
services, Ruma,
};
/// Checks if the room is banned in any way possible and the sender user is not
@@ -200,7 +202,7 @@ pub(crate) async fn join_room_by_id_route(
}
join_room_by_id_helper(
body.sender_user.as_deref(),
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
@@ -299,7 +301,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
};
let join_room_response = join_room_by_id_helper(
Some(sender_user),
sender_user,
&room_id,
body.reason.clone(),
&servers,
@@ -364,6 +366,8 @@ pub(crate) async fn invite_user_route(
pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -381,12 +385,6 @@ pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Resul
event.membership = MembershipState::Leave;
event.reason.clone_from(&body.reason);
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
services()
.rooms
.timeline
@@ -415,6 +413,8 @@ pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Resul
pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let event = services()
.rooms
.state_accessor
@@ -445,12 +445,6 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
},
)?;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
services()
.rooms
.timeline
@@ -479,6 +473,8 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
pub(crate) async fn unban_user_route(body: Ruma<unban_user::v3::Request>) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -494,12 +490,6 @@ pub(crate) async fn unban_user_route(body: Ruma<unban_user::v3::Request>) -> Res
event.reason.clone_from(&body.reason);
event.join_authorized_via_users_server = None;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
services()
.rooms
.timeline
@@ -651,35 +641,36 @@ pub(crate) async fn joined_members_route(
}
pub async fn join_room_by_id_helper(
sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = sender_user.expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(room_id).await;
if matches!(services().rooms.state_cache.is_joined(sender_user, room_id), Ok(true)) {
info!("{sender_user} is already joined in {room_id}");
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response {
room_id: room_id.into(),
});
}
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
// Ask a remote server if we are not participating in this room
if !services()
if services()
.rooms
.state_cache
.server_in_room(services().globals.server_name(), room_id)?
|| servers.is_empty()
|| (servers.len() == 1 && server_is_ours(&servers[0]))
{
join_room_by_id_helper_remote(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
} else {
join_room_by_id_helper_local(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
}
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: mutex_map::Guard<()>,
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
info!("Joining {room_id} over federation.");
@@ -789,14 +780,9 @@ async fn join_room_by_id_helper_remote(
info!("send_join finished");
if join_authorized_via_users_server.is_some() {
use RoomVersionId::*;
match &room_version_id {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7 => {
V1 | V2 | V3 | V4 | V5 | V6 | V7 => {
warn!(
"Found `join_authorised_via_users_server` but room {} is version {}. Ignoring.",
room_id, &room_version_id
@@ -804,7 +790,7 @@ async fn join_room_by_id_helper_remote(
},
// only room versions 8 and above using `join_authorized_via_users_server` (restricted joins) need to
// validate and send signatures
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 => {
V8 | V9 | V10 | V11 => {
if let Some(signed_raw) = &send_join_response.room_state.event {
info!(
"There is a signed event. This room is probably using restricted joins. Adding signature to \
@@ -1012,11 +998,12 @@ async fn join_room_by_id_helper_remote(
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: mutex_map::Guard<()>,
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
info!("We can join locally");
debug!("We can join locally");
let join_rules_event =
services()
@@ -1116,7 +1103,7 @@ async fn join_room_by_id_helper_local(
.iter()
.any(|server_name| !server_is_ours(server_name))
{
info!("We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements");
warn!("We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements");
let (make_join_response, remote_server) = make_join_request(sender_user, room_id, servers).await?;
let room_version_id = match make_join_response.room_version {
@@ -1281,16 +1268,12 @@ async fn make_join_request(
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
trace!("make_join ErrorKind string: {:?}", e.error_code().to_string());
trace!("make_join ErrorKind string: {:?}", e.kind().to_string());
// converting to a string is necessary (i think) because ruma is forcing us to
// fill in the struct for M_INCOMPATIBLE_ROOM_VERSION
if e.error_code()
.to_string()
.contains("M_INCOMPATIBLE_ROOM_VERSION")
|| e.error_code()
.to_string()
.contains("M_UNSUPPORTED_ROOM_VERSION")
if e.kind().to_string().contains("M_INCOMPATIBLE_ROOM_VERSION")
|| e.kind().to_string().contains("M_UNSUPPORTED_ROOM_VERSION")
{
incompatible_room_version_count = incompatible_room_version_count.saturating_add(1);
}
@@ -1363,11 +1346,10 @@ pub async fn validate_and_add_event_id(
.get(&event_id)
{
// Exponential backoff
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", event_id);
const MIN: u64 = 60 * 5;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off from {event_id}");
return Err(Error::BadServerResponse("bad event, still backing off"));
}
}
@@ -1396,7 +1378,7 @@ pub(crate) async fn invite_helper(
if !user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let content = to_raw_value(&RoomMemberEventContent {
avatar_url: services().users.avatar_url(user_id)?,
displayname: None,
@@ -1508,7 +1490,7 @@ pub(crate) async fn invite_helper(
));
}
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
services()
.rooms
@@ -1602,7 +1584,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
true,
)?;
} else {
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let member_event =
services()
@@ -1681,8 +1663,7 @@ async fn remote_leave_room(user_id: &UserId, room_id: &RoomId) -> Result<()> {
.filter_map(|event: serde_json::Value| event.get("sender").cloned())
.filter_map(|sender| sender.as_str().map(ToOwned::to_owned))
.filter_map(|sender| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned())
.collect::<HashSet<OwnedServerName>>(),
.map(|user| user.server_name().to_owned()),
);
debug!("servers in remote_leave_room: {servers:?}");
+1 -5
View File
@@ -29,11 +29,7 @@ pub(crate) async fn send_message_event_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref();
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services().globals.allow_encryption() {
+1 -1
View File
@@ -353,7 +353,7 @@ pub async fn update_avatar_url(
pub async fn update_all_rooms(all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId) {
for (pdu_builder, room_id) in all_joined_rooms {
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
if let Err(e) = services()
.rooms
.timeline
+1 -5
View File
@@ -15,11 +15,7 @@ pub(crate) async fn redact_event_route(body: Ruma<redact_event::v3::Request>) ->
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let event_id = services()
.rooms
+35 -64
View File
@@ -90,7 +90,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
}
let _short_id = services().rooms.short.get_or_create_shortroomid(&room_id)?;
let state_lock = services().globals.roomid_mutex_state.lock(&room_id).await;
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name {
Some(room_alias_check(alias, &body.appservice_info).await?)
@@ -118,6 +118,8 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
let content = match &body.creation_content {
Some(content) => {
use RoomVersionId::*;
let mut content = content
.deserialize_as::<CanonicalJsonObject>()
.map_err(|e| {
@@ -125,16 +127,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
Error::bad_database("Failed to deserialise content as canonical JSON.")
})?;
match room_version {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10 => {
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
@@ -143,7 +136,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
})?,
);
},
RoomVersionId::V11 => {}, // V11 removed the "creator" key
V11 => {}, // V11 removed the "creator" key
_ => {
warn!("Unexpected or unsupported room version {room_version}");
return Err(Error::BadRequest(
@@ -152,7 +145,6 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
));
},
}
content.insert(
"room_version".into(),
json!(room_version.as_str())
@@ -162,18 +154,11 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
content
},
None => {
use RoomVersionId::*;
let content = match room_version {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10 => RoomCreateEventContent::new_v1(sender_user.clone()),
RoomVersionId::V11 => RoomCreateEventContent::new_v11(),
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => RoomCreateEventContent::new_v1(sender_user.clone()),
V11 => RoomCreateEventContent::new_v11(),
_ => {
warn!("Unexpected or unsupported room version {room_version}");
return Err(Error::BadRequest(
@@ -573,11 +558,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
.short
.get_or_create_shortroomid(&replacement_room)?;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
@@ -605,11 +586,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Change lock to replacement room
drop(state_lock);
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&replacement_room)
.await;
let state_lock = services().rooms.state.mutex.lock(&replacement_room).await;
// Get the old room creation event
let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
@@ -631,36 +608,30 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Send a m.room.create event containing a predecessor field and the applicable
// room_version
match body.new_version {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10 => {
create_event_content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {e}");
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
})?,
);
},
RoomVersionId::V11 => {
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
_ => {
warn!("Unexpected or unsupported room version {}", body.new_version);
return Err(Error::BadRequest(
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
},
{
use RoomVersionId::*;
match body.new_version {
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
create_event_content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {e}");
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
})?,
);
},
V11 => {
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
_ => {
warn!("Unexpected or unsupported room version {}", body.new_version);
return Err(Error::BadRequest(
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
},
}
}
create_event_content.insert(
+1 -1
View File
@@ -47,7 +47,7 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
&body.room_id,
limit.try_into().unwrap_or(10),
key.map_or(vec![], |token| token.short_room_ids),
max_depth.try_into().unwrap_or(3),
max_depth.into(),
body.suggested_only,
)
.await
+12 -14
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use conduit::{error, warn};
use conduit::{debug_info, error};
use ruma::{
api::client::{
error::ErrorKind,
@@ -36,18 +36,16 @@ pub(crate) async fn send_state_event_for_key_route(
) -> Result<send_state_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event_id = send_state_event_for_key_helper(
sender_user,
&body.room_id,
&body.event_type,
&body.body.body,
body.state_key.clone(),
)
.await?;
let event_id = (*event_id).to_owned();
Ok(send_state_event::v3::Response {
event_id,
event_id: send_state_event_for_key_helper(
sender_user,
&body.room_id,
&body.event_type,
&body.body.body,
body.state_key.clone(),
)
.await?
.into(),
})
}
@@ -128,7 +126,7 @@ pub(crate) async fn get_state_events_for_key_route(
.state_accessor
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
.ok_or_else(|| {
warn!("State event {:?} not found in room {:?}", &body.event_type, &body.room_id);
debug_info!("State event {:?} not found in room {:?}", &body.event_type, &body.room_id);
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
})?;
if body
@@ -172,7 +170,7 @@ async fn send_state_event_for_key_helper(
sender: &UserId, room_id: &RoomId, event_type: &StateEventType, json: &Raw<AnyStateEventContent>, state_key: String,
) -> Result<Arc<EventId>> {
allowed_to_send_state_event(room_id, event_type, json).await?;
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let event_id = services()
.rooms
.timeline
+30 -35
View File
@@ -1,10 +1,15 @@
use std::{
cmp,
cmp::Ordering,
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
time::Duration,
};
use conduit::PduCount;
use conduit::{
error,
utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
Err, PduCount,
};
use ruma::{
api::client::{
filter::{FilterDefinition, LazyLoadOptions},
@@ -27,7 +32,7 @@ use ruma::{
serde::Raw,
uint, DeviceId, EventId, OwnedUserId, RoomId, UInt, UserId,
};
use tracing::{error, Instrument as _, Span};
use tracing::{Instrument as _, Span};
use crate::{service::pdu::EventHash, services, utils, Error, PduEvent, Result, Ruma, RumaResponse};
@@ -194,7 +199,7 @@ pub(crate) async fn sync_events_route(
let (room_id, invite_state_events) = result?;
// Get and drop the lock to wait for remaining operations to finish
let insert_lock = services().globals.roomid_mutex_insert.lock(&room_id).await;
let insert_lock = services().rooms.timeline.mutex_insert.lock(&room_id).await;
drop(insert_lock);
let invite_count = services()
@@ -298,15 +303,9 @@ pub(crate) async fn sync_events_route(
{
// Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives
let mut duration = body.timeout.unwrap_or_default();
if duration.as_secs() > 30 {
duration = Duration::from_secs(30);
}
#[allow(clippy::let_underscore_must_use)]
{
_ = tokio::time::timeout(duration, watcher).await;
}
let default = Duration::from_secs(30);
let duration = cmp::min(body.timeout.unwrap_or(default), default);
_ = tokio::time::timeout(duration, watcher).await;
}
Ok(response)
@@ -318,7 +317,7 @@ async fn handle_left_room(
next_batch_string: &str, full_state: bool, lazy_load_enabled: bool,
) -> Result<()> {
// Get and drop the lock to wait for remaining operations to finish
let insert_lock = services().globals.roomid_mutex_insert.lock(room_id).await;
let insert_lock = services().rooms.timeline.mutex_insert.lock(room_id).await;
drop(insert_lock);
let left_count = services()
@@ -520,7 +519,7 @@ async fn load_joined_room(
) -> Result<JoinedRoom> {
// Get and drop the lock to wait for remaining operations to finish
// This will make sure the we have all events until next_batch
let insert_lock = services().globals.roomid_mutex_insert.lock(room_id).await;
let insert_lock = services().rooms.timeline.mutex_insert.lock(room_id).await;
drop(insert_lock);
let (timeline_pdus, limited) = load_timeline(sender_user, room_id, sincecount, 10)?;
@@ -546,8 +545,7 @@ async fn load_joined_room(
// Database queries:
let Some(current_shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else {
error!("Room {} has no state", room_id);
return Err(Error::BadDatabase("Room has no state"));
return Err!(Database(error!("Room {room_id} has no state")));
};
let since_shortstatehash = services()
@@ -975,8 +973,8 @@ async fn load_joined_room(
},
summary: RoomSummary {
heroes,
joined_member_count: joined_member_count.map(|n| (n as u32).into()),
invited_member_count: invited_member_count.map(|n| (n as u32).into()),
joined_member_count: joined_member_count.map(ruma_from_u64),
invited_member_count: invited_member_count.map(ruma_from_u64),
},
unread_notifications: UnreadNotificationsCount {
highlight_count,
@@ -1026,7 +1024,7 @@ fn load_timeline(
// Take the last events for the timeline
timeline_pdus = non_timeline_pdus
.by_ref()
.take(limit as usize)
.take(usize_from_u64_truncated(limit))
.collect::<Vec<_>>()
.into_iter()
.rev()
@@ -1300,7 +1298,7 @@ pub(crate) async fn sync_events_v4_route(
r.0,
UInt::try_from(all_joined_rooms.len().saturating_sub(1)).unwrap_or(UInt::MAX),
);
let room_ids = all_joined_rooms[(u64::from(r.0) as usize)..=(u64::from(r.1) as usize)].to_vec();
let room_ids = all_joined_rooms[usize_from_ruma(r.0)..=usize_from_ruma(r.1)].to_vec();
new_known_rooms.extend(room_ids.iter().cloned());
for room_id in &room_ids {
let todo_room = todo_rooms
@@ -1333,7 +1331,7 @@ pub(crate) async fn sync_events_v4_route(
}
})
.collect(),
count: UInt::from(all_joined_rooms.len() as u32),
count: ruma_from_usize(all_joined_rooms.len()),
},
);
@@ -1529,20 +1527,22 @@ pub(crate) async fn sync_events_v4_route(
prev_batch,
limited,
joined_count: Some(
(services()
services()
.rooms
.state_cache
.room_joined_count(room_id)?
.unwrap_or(0) as u32)
.into(),
.unwrap_or(0)
.try_into()
.unwrap_or_else(|_| uint!(0)),
),
invited_count: Some(
(services()
services()
.rooms
.state_cache
.room_invited_count(room_id)?
.unwrap_or(0) as u32)
.into(),
.unwrap_or(0)
.try_into()
.unwrap_or_else(|_| uint!(0)),
),
num_live: None, // Count events in timeline greater than global sync counter
timestamp: None,
@@ -1557,14 +1557,9 @@ pub(crate) async fn sync_events_v4_route(
{
// Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives
let mut duration = body.timeout.unwrap_or(Duration::from_secs(30));
if duration.as_secs() > 30 {
duration = Duration::from_secs(30);
}
#[allow(clippy::let_underscore_must_use)]
{
_ = tokio::time::timeout(duration, watcher).await;
}
let default = Duration::from_secs(30);
let duration = cmp::min(body.timeout.unwrap_or(default), default);
_ = tokio::time::timeout(duration, watcher).await;
}
Ok(sync_events::v4::Response {
+11 -1
View File
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use ruma::api::client::thirdparty::get_protocols;
use crate::{Result, Ruma};
use crate::{Result, Ruma, RumaResponse};
/// # `GET /_matrix/client/r0/thirdparty/protocols`
///
@@ -15,3 +15,13 @@ pub(crate) async fn get_protocols_route(
protocols: BTreeMap::new(),
})
}
/// # `GET /_matrix/client/unstable/thirdparty/protocols`
///
/// Same as `get_protocols_route`, except for some reason Element Android legacy
/// calls this
pub(crate) async fn get_protocols_route_unstable(
body: Ruma<get_protocols::v3::Request>,
) -> Result<RumaResponse<get_protocols::v3::Response>> {
get_protocols_route(body).await.map(RumaResponse)
}
+2 -2
View File
@@ -1,12 +1,12 @@
use axum_client_ip::InsecureClientIp;
use conduit::{warn, RumaResponse};
use conduit::warn;
use ruma::{
api::client::{error::ErrorKind, membership::mutual_rooms, room::get_summary},
events::room::member::MembershipState,
OwnedRoomId,
};
use crate::{services, Error, Result, Ruma};
use crate::{services, Error, Result, Ruma, RumaResponse};
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
///
+4 -4
View File
@@ -1,14 +1,14 @@
pub mod client;
mod router;
pub mod routes;
pub mod router;
pub mod server;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{debug_info, debug_warn, utils, Error, Result};
pub(crate) use service::{pdu::PduEvent, services, user_is_local};
pub(crate) use conduit::{debug_info, debug_warn, pdu::PduEvent, utils, Error, Result};
pub(crate) use service::{services, user_is_local};
pub use crate::router::State;
pub(crate) use crate::router::{Ruma, RumaResponse};
conduit::mod_ctor! {}
+26 -15
View File
@@ -1,15 +1,24 @@
mod args;
mod auth;
mod handler;
mod request;
mod response;
use axum::{
response::IntoResponse,
routing::{any, get, post},
Router,
};
use conduit::{Error, Server};
use conduit::{err, Server};
use http::Uri;
use ruma::api::client::error::ErrorKind;
use crate::{client, router::RouterExt, server};
use self::handler::RouterExt;
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
use crate::{client, server};
pub fn build(router: Router, server: &Server) -> Router {
pub type State = &'static service::Services;
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config;
let router = router
.ruma_route(client::get_supported_versions_route)
@@ -94,6 +103,8 @@ pub fn build(router: Router, server: &Server) -> Router {
.ruma_route(client::search_users_route)
.ruma_route(client::get_member_events_route)
.ruma_route(client::get_protocols_route)
.route("/_matrix/client/unstable/thirdparty/protocols",
get(client::get_protocols_route_unstable))
.ruma_route(client::send_message_event_route)
.ruma_route(client::send_state_event_for_key_route)
.ruma_route(client::get_state_events_route)
@@ -178,15 +189,15 @@ pub fn build(router: Router, server: &Server) -> Router {
.ruma_route(client::get_relating_events_with_rel_type_route)
.ruma_route(client::get_relating_events_route)
.ruma_route(client::get_hierarchy_route)
.ruma_route(client::get_mutual_rooms_route)
.ruma_route(client::get_room_summary)
.route(
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
get(client::get_room_summary_legacy)
)
.ruma_route(client::well_known_support)
.ruma_route(client::well_known_client)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.ruma_route(client::get_mutual_rooms_route)
.ruma_route(client::get_room_summary)
.route(
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
get(client::get_room_summary_legacy)
)
.ruma_route(client::well_known_support)
.ruma_route(client::well_known_client)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.route("/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync))
.route("/_matrix/client/v3/rooms/:room_id/initialSync", get(initial_sync))
.route("/client/server.json", get(client::syncv3_client_server_json));
@@ -231,7 +242,7 @@ pub fn build(router: Router, server: &Server) -> Router {
}
async fn initial_sync(_uri: Uri) -> impl IntoResponse {
Error::BadRequest(ErrorKind::GuestAccessForbidden, "Guest access not implemented")
err!(Request(GuestAccessForbidden("Guest access not implemented")))
}
async fn federation_disabled() -> impl IntoResponse { Error::bad_config("Federation is disabled.") }
async fn federation_disabled() -> impl IntoResponse { err!(Config("allow_federation", "Federation is disabled.")) }
@@ -1,24 +1,15 @@
mod auth;
mod handler;
mod request;
use std::{mem, ops::Deref};
use axum::{async_trait, body::Body, extract::FromRequest};
use bytes::{BufMut, BytesMut};
pub(super) use conduit::error::RumaResponse;
use conduit::{debug, debug_warn, trace, warn};
use ruma::{
api::{client::error::ErrorKind, IncomingRequest},
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
};
use conduit::{debug, err, trace, Error, Result};
use ruma::{api::IncomingRequest, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId};
pub(super) use self::handler::RouterExt;
use self::{auth::Auth, request::Request};
use crate::{service::appservice::RegistrationInfo, services, Error, Result};
use super::{auth, auth::Auth, request, request::Request};
use crate::{service::appservice::RegistrationInfo, services};
/// Extractor for Ruma request structs
pub(crate) struct Ruma<T> {
pub(crate) struct Args<T> {
/// Request struct body
pub(crate) body: T,
@@ -44,7 +35,7 @@ pub(crate) struct Ruma<T> {
}
#[async_trait]
impl<T, S> FromRequest<S, Body> for Ruma<T>
impl<T, S> FromRequest<S, Body> for Args<T>
where
T: IncomingRequest,
{
@@ -65,7 +56,7 @@ where
}
}
impl<T> Deref for Ruma<T> {
impl<T> Deref for Args<T> {
type Target = T;
fn deref(&self) -> &Self::Target { &self.body }
@@ -109,21 +100,14 @@ where
let mut http_request = hyper::Request::builder()
.uri(request.parts.uri.clone())
.method(request.parts.method.clone());
*http_request.headers_mut().unwrap() = request.parts.headers.clone();
let http_request = http_request.body(body).unwrap();
debug!(
"{:?} {:?} {:?}",
http_request.method(),
http_request.uri(),
http_request.headers()
);
*http_request.headers_mut().expect("mutable http headers") = request.parts.headers.clone();
let http_request = http_request.body(body).expect("http request body");
trace!("{:?} {:?} {:?}", http_request.method(), http_request.uri(), json_body);
let body = T::try_from_http_request(http_request, &request.path).map_err(|e| {
warn!("try_from_http_request failed: {e:?}",);
debug_warn!("JSON body: {:?}", json_body);
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
})?;
let headers = http_request.headers();
let method = http_request.method();
let uri = http_request.uri();
debug!("{method:?} {uri:?} {headers:?}");
trace!("{method:?} {uri:?} {json_body:?}");
Ok(body)
T::try_from_http_request(http_request, &request.path).map_err(|e| err!(Request(BadJson(debug_warn!("{e}")))))
}
+2 -1
View File
@@ -6,6 +6,7 @@ use axum_extra::{
typed_header::TypedHeaderRejectionReason,
TypedHeader,
};
use conduit::Err;
use http::uri::PathAndQuery;
use ruma::{
api::{client::error::ErrorKind, AuthScheme, Metadata},
@@ -183,7 +184,7 @@ fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Aut
async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValue>) -> Result<Auth> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
return Err!(Config("allow_federation", "Federation is disabled."));
}
let TypedHeader(Authorization(x_matrix)) = request
+8 -8
View File
@@ -10,7 +10,7 @@ use conduit::Result;
use http::Method;
use ruma::api::IncomingRequest;
use super::{Ruma, RumaResponse};
use super::{Ruma, RumaResponse, State};
pub(in super::super) trait RouterExt {
fn ruma_route<H, T>(self, handler: H) -> Self
@@ -18,7 +18,7 @@ pub(in super::super) trait RouterExt {
H: RumaHandler<T>;
}
impl RouterExt for Router {
impl RouterExt for Router<State> {
fn ruma_route<H, T>(self, handler: H) -> Self
where
H: RumaHandler<T>,
@@ -28,9 +28,9 @@ impl RouterExt for Router {
}
pub(in super::super) trait RumaHandler<T> {
fn add_routes(&self, router: Router) -> Router;
fn add_routes(&self, router: Router<State>) -> Router<State>;
fn add_route(&self, router: Router, path: &str) -> Router;
fn add_route(&self, router: Router<State>, path: &str) -> Router<State>;
}
macro_rules! ruma_handler {
@@ -41,17 +41,17 @@ macro_rules! ruma_handler {
Req: IncomingRequest + Send + 'static,
Ret: IntoResponse,
Fut: Future<Output = Result<Req::OutgoingResponse, Ret>> + Send,
Fun: FnOnce($($tx,)* Ruma<Req>) -> Fut + Clone + Send + Sync + 'static,
$( $tx: FromRequestParts<()> + Send + 'static, )*
Fun: FnOnce($($tx,)* Ruma<Req>,) -> Fut + Clone + Send + Sync + 'static,
$( $tx: FromRequestParts<State> + Send + 'static, )*
{
fn add_routes(&self, router: Router) -> Router {
fn add_routes(&self, router: Router<State>) -> Router<State> {
Req::METADATA
.history
.all_paths()
.fold(router, |router, path| self.add_route(router, path))
}
fn add_route(&self, router: Router, path: &str) -> Router {
fn add_route(&self, router: Router<State>, path: &str) -> Router<State> {
let handle = self.clone();
let method = method_to_filter(&Req::METADATA.method);
let action = |$($tx,)* req| async { handle($($tx,)* req).await.map(RumaResponse) };
+7 -11
View File
@@ -2,11 +2,11 @@ use std::str;
use axum::{extract::Path, RequestExt, RequestPartsExt};
use bytes::Bytes;
use conduit::err;
use http::request::Parts;
use ruma::api::client::error::ErrorKind;
use serde::Deserialize;
use crate::{services, Error, Result};
use crate::{services, Result};
#[derive(Deserialize)]
pub(super) struct QueryParams {
@@ -26,19 +26,15 @@ pub(super) async fn from(request: hyper::Request<axum::body::Body>) -> Result<Re
let (mut parts, body) = limited.into_parts();
let path: Path<Vec<String>> = parts.extract().await?;
let query = serde_html_form::from_str(parts.uri.query().unwrap_or_default())
.map_err(|_| Error::BadRequest(ErrorKind::Unknown, "Failed to read query parameters"))?;
let query = parts.uri.query().unwrap_or_default();
let query =
serde_html_form::from_str(query).map_err(|e| err!(Request(Unknown("Failed to read query parameters: {e}"))))?;
let max_body_size = services()
.globals
.config
.max_request_size
.try_into()
.expect("failed to convert max request size");
let max_body_size = services().globals.config.max_request_size;
let body = axum::body::to_bytes(body, max_body_size)
.await
.map_err(|_| Error::BadRequest(ErrorKind::TooLarge, "Request body too large"))?;
.map_err(|e| err!(Request(TooLarge("Request body too large: {e}"))))?;
Ok(Request {
path,
+24
View File
@@ -0,0 +1,24 @@
use axum::response::{IntoResponse, Response};
use bytes::BytesMut;
use conduit::{error, Error};
use http::StatusCode;
use http_body_util::Full;
use ruma::api::{client::uiaa::UiaaResponse, OutgoingResponse};
pub(crate) struct RumaResponse<T>(pub(crate) T);
impl From<Error> for RumaResponse<UiaaResponse> {
fn from(t: Error) -> Self { Self(t.into()) }
}
impl<T: OutgoingResponse> IntoResponse for RumaResponse<T> {
fn into_response(self) -> Response {
self.0
.try_into_http_response::<BytesMut>()
.inspect_err(|e| error!("response error: {e}"))
.map_or_else(
|_| StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|r| r.map(BytesMut::freeze).map(Full::new).into_response(),
)
}
}
+26 -37
View File
@@ -7,7 +7,7 @@ use ruma::{
},
StateEventType, TimelineEventType,
},
RoomId, RoomVersionId, UserId,
CanonicalJsonObject, RoomId, RoomVersionId, UserId,
};
use serde_json::value::to_raw_value;
use tracing::warn;
@@ -71,11 +71,7 @@ pub(crate) async fn create_join_event_template_route(
let room_version_id = services().rooms.state.get_room_version(&body.room_id)?;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let join_authorized_via_users_server = if (services()
.rooms
@@ -148,27 +144,7 @@ pub(crate) async fn create_join_event_template_route(
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
RoomVersionId::V1 | RoomVersionId::V2 => {},
RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10
| RoomVersionId::V11 => {
pdu_json.remove("event_id");
},
_ => {
warn!("Unexpected or unsupported room version {room_version_id}");
return Err(Error::BadRequest(
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
},
};
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
Ok(prepare_join_event::v1::Response {
room_version: Some(room_version_id),
@@ -183,6 +159,8 @@ pub(crate) async fn create_join_event_template_route(
pub(crate) fn user_can_perform_restricted_join(
user_id: &UserId, room_id: &RoomId, room_version_id: &RoomVersionId,
) -> Result<bool> {
use RoomVersionId::*;
let join_rules_event =
services()
.rooms
@@ -202,16 +180,7 @@ pub(crate) fn user_can_perform_restricted_join(
return Ok(false);
};
if matches!(
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
) {
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
return Ok(false);
}
@@ -243,3 +212,23 @@ pub(crate) fn user_can_perform_restricted_join(
))
}
}
pub(crate) fn maybe_strip_event_id(pdu_json: &mut CanonicalJsonObject, room_version_id: &RoomVersionId) -> Result<()> {
use RoomVersionId::*;
match room_version_id {
V1 | V2 => {},
V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 => {
pdu_json.remove("event_id");
},
_ => {
warn!("Unexpected or unsupported room version {room_version_id}");
return Err(Error::BadRequest(
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
},
};
Ok(())
}
+5 -27
View File
@@ -1,14 +1,15 @@
use conduit::{Error, Result};
use ruma::{
api::{client::error::ErrorKind, federation::membership::prepare_leave_event},
events::{
room::member::{MembershipState, RoomMemberEventContent},
TimelineEventType,
},
RoomVersionId,
};
use serde_json::value::to_raw_value;
use crate::{service::pdu::PduBuilder, services, Error, Result, Ruma};
use super::make_join::maybe_strip_event_id;
use crate::{service::pdu::PduBuilder, services, Ruma};
/// # `PUT /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
///
@@ -35,11 +36,7 @@ pub(crate) async fn create_leave_event_template_route(
.acl_check(origin, &body.room_id)?;
let room_version_id = services().rooms.state.get_room_version(&body.room_id)?;
let state_lock = services()
.globals
.roomid_mutex_state
.lock(&body.room_id)
.await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let content = to_raw_value(&RoomMemberEventContent {
avatar_url: None,
blurhash: None,
@@ -68,26 +65,7 @@ pub(crate) async fn create_leave_event_template_route(
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
RoomVersionId::V1 | RoomVersionId::V2 => {},
RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10
| RoomVersionId::V11 => {
pdu_json.remove("event_id");
},
_ => {
return Err(Error::BadRequest(
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
},
};
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
Ok(prepare_leave_event::v1::Response {
room_version: Some(room_version_id),
+63 -62
View File
@@ -1,7 +1,8 @@
use std::{collections::BTreeMap, net::IpAddr, time::Instant};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::debug_warn;
use conduit::{debug, debug_warn, err, trace, warn, Err};
use ruma::{
api::{
client::error::ErrorKind,
@@ -18,11 +19,10 @@ use ruma::{
OwnedEventId, ServerName,
};
use tokio::sync::RwLock;
use tracing::{debug, error, trace, warn};
use crate::{
service::rooms::event_handler::parse_incoming_pdu,
services,
services::Services,
utils::{self},
Error, Result, Ruma,
};
@@ -34,29 +34,23 @@ type ResolvedMap = BTreeMap<OwnedEventId, Result<(), Error>>;
/// Push EDUs and PDUs to this server.
#[tracing::instrument(skip_all, fields(%client), name = "send")]
pub(crate) async fn send_transaction_message_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<send_transaction_message::v1::Request>,
State(services): State<&Services>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<send_transaction_message::v1::Request>,
) -> Result<send_transaction_message::v1::Response> {
let origin = body.origin.as_ref().expect("server is authenticated");
if *origin != body.body.origin {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Not allowed to send transactions on behalf of other servers",
));
return Err!(Request(Forbidden(
"Not allowed to send transactions on behalf of other servers"
)));
}
if body.pdus.len() > 50_usize {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Not allowed to send more than 50 PDUs in one transaction",
));
return Err!(Request(Forbidden("Not allowed to send more than 50 PDUs in one transaction")));
}
if body.edus.len() > 100_usize {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Not allowed to send more than 100 EDUs in one transaction",
));
return Err!(Request(Forbidden("Not allowed to send more than 100 EDUs in one transaction")));
}
let txn_start_time = Instant::now();
@@ -69,8 +63,8 @@ pub(crate) async fn send_transaction_message_route(
"Starting txn",
);
let resolved_map = handle_pdus(&client, &body, origin, &txn_start_time).await?;
handle_edus(&client, &body, origin).await?;
let resolved_map = handle_pdus(services, &client, &body, origin, &txn_start_time).await?;
handle_edus(services, &client, &body, origin).await?;
debug!(
pdus = ?body.pdus.len(),
@@ -84,13 +78,14 @@ pub(crate) async fn send_transaction_message_route(
Ok(send_transaction_message::v1::Response {
pdus: resolved_map
.into_iter()
.map(|(e, r)| (e, r.map_err(|e| e.sanitized_error())))
.map(|(e, r)| (e, r.map_err(|e| e.sanitized_string())))
.collect(),
})
}
async fn handle_pdus(
_client: &IpAddr, body: &Ruma<send_transaction_message::v1::Request>, origin: &ServerName, txn_start_time: &Instant,
services: &Services, _client: &IpAddr, body: &Ruma<send_transaction_message::v1::Request>, origin: &ServerName,
txn_start_time: &Instant,
) -> Result<ResolvedMap> {
let mut parsed_pdus = Vec::with_capacity(body.pdus.len());
for pdu in &body.pdus {
@@ -110,7 +105,7 @@ async fn handle_pdus(
// corresponding signing keys
let pub_key_map = RwLock::new(BTreeMap::new());
if !parsed_pdus.is_empty() {
services()
services
.rooms
.event_handler
.fetch_required_signing_keys(parsed_pdus.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
@@ -126,14 +121,15 @@ async fn handle_pdus(
let mut resolved_map = BTreeMap::new();
for (event_id, value, room_id) in parsed_pdus {
let pdu_start_time = Instant::now();
let mutex_lock = services()
.globals
.roomid_mutex_federation
let mutex_lock = services
.rooms
.event_handler
.mutex_federation
.lock(&room_id)
.await;
resolved_map.insert(
event_id.clone(),
services()
services
.rooms
.event_handler
.handle_incoming_pdu(origin, &room_id, &event_id, value, true, &pub_key_map)
@@ -161,7 +157,7 @@ async fn handle_pdus(
}
async fn handle_edus(
client: &IpAddr, body: &Ruma<send_transaction_message::v1::Request>, origin: &ServerName,
services: &Services, client: &IpAddr, body: &Ruma<send_transaction_message::v1::Request>, origin: &ServerName,
) -> Result<()> {
for edu in body
.edus
@@ -169,12 +165,12 @@ async fn handle_edus(
.filter_map(|edu| serde_json::from_str::<Edu>(edu.json().get()).ok())
{
match edu {
Edu::Presence(presence) => handle_edu_presence(client, origin, presence).await?,
Edu::Receipt(receipt) => handle_edu_receipt(client, origin, receipt).await?,
Edu::Typing(typing) => handle_edu_typing(client, origin, typing).await?,
Edu::DeviceListUpdate(content) => handle_edu_device_list_update(client, origin, content).await?,
Edu::DirectToDevice(content) => handle_edu_direct_to_device(client, origin, content).await?,
Edu::SigningKeyUpdate(content) => handle_edu_signing_key_update(client, origin, content).await?,
Edu::Presence(presence) => handle_edu_presence(services, client, origin, presence).await?,
Edu::Receipt(receipt) => handle_edu_receipt(services, client, origin, receipt).await?,
Edu::Typing(typing) => handle_edu_typing(services, client, origin, typing).await?,
Edu::DeviceListUpdate(content) => handle_edu_device_list_update(services, client, origin, content).await?,
Edu::DirectToDevice(content) => handle_edu_direct_to_device(services, client, origin, content).await?,
Edu::SigningKeyUpdate(content) => handle_edu_signing_key_update(services, client, origin, content).await?,
Edu::_Custom(ref _custom) => {
debug_warn!(?body.edus, "received custom/unknown EDU");
},
@@ -184,8 +180,10 @@ async fn handle_edus(
Ok(())
}
async fn handle_edu_presence(_client: &IpAddr, origin: &ServerName, presence: PresenceContent) -> Result<()> {
if !services().globals.allow_incoming_presence() {
async fn handle_edu_presence(
services: &Services, _client: &IpAddr, origin: &ServerName, presence: PresenceContent,
) -> Result<()> {
if !services.globals.allow_incoming_presence() {
return Ok(());
}
@@ -198,7 +196,7 @@ async fn handle_edu_presence(_client: &IpAddr, origin: &ServerName, presence: Pr
continue;
}
services().presence.set_presence(
services.presence.set_presence(
&update.user_id,
&update.presence,
Some(update.currently_active),
@@ -210,13 +208,15 @@ async fn handle_edu_presence(_client: &IpAddr, origin: &ServerName, presence: Pr
Ok(())
}
async fn handle_edu_receipt(_client: &IpAddr, origin: &ServerName, receipt: ReceiptContent) -> Result<()> {
if !services().globals.allow_incoming_read_receipts() {
async fn handle_edu_receipt(
services: &Services, _client: &IpAddr, origin: &ServerName, receipt: ReceiptContent,
) -> Result<()> {
if !services.globals.allow_incoming_read_receipts() {
return Ok(());
}
for (room_id, room_updates) in receipt.receipts {
if services()
if services
.rooms
.event_handler
.acl_check(origin, &room_id)
@@ -238,7 +238,7 @@ async fn handle_edu_receipt(_client: &IpAddr, origin: &ServerName, receipt: Rece
continue;
}
if services()
if services
.rooms
.state_cache
.room_members(&room_id)
@@ -254,7 +254,7 @@ async fn handle_edu_receipt(_client: &IpAddr, origin: &ServerName, receipt: Rece
room_id: room_id.clone(),
};
services()
services
.rooms
.read_receipt
.readreceipt_update(&user_id, &room_id, &event)?;
@@ -272,8 +272,10 @@ async fn handle_edu_receipt(_client: &IpAddr, origin: &ServerName, receipt: Rece
Ok(())
}
async fn handle_edu_typing(_client: &IpAddr, origin: &ServerName, typing: TypingContent) -> Result<()> {
if !services().globals.config.allow_incoming_typing {
async fn handle_edu_typing(
services: &Services, _client: &IpAddr, origin: &ServerName, typing: TypingContent,
) -> Result<()> {
if !services.globals.config.allow_incoming_typing {
return Ok(());
}
@@ -285,7 +287,7 @@ async fn handle_edu_typing(_client: &IpAddr, origin: &ServerName, typing: Typing
return Ok(());
}
if services()
if services
.rooms
.event_handler
.acl_check(typing.user_id.server_name(), &typing.room_id)
@@ -298,26 +300,26 @@ async fn handle_edu_typing(_client: &IpAddr, origin: &ServerName, typing: Typing
return Ok(());
}
if services()
if services
.rooms
.state_cache
.is_joined(&typing.user_id, &typing.room_id)?
{
if typing.typing {
let timeout = utils::millis_since_unix_epoch().saturating_add(
services()
services
.globals
.config
.typing_federation_timeout_s
.saturating_mul(1000),
);
services()
services
.rooms
.typing
.typing_add(&typing.user_id, &typing.room_id, timeout)
.await?;
} else {
services()
services
.rooms
.typing
.typing_remove(&typing.user_id, &typing.room_id)
@@ -335,7 +337,7 @@ async fn handle_edu_typing(_client: &IpAddr, origin: &ServerName, typing: Typing
}
async fn handle_edu_device_list_update(
_client: &IpAddr, origin: &ServerName, content: DeviceListUpdateContent,
services: &Services, _client: &IpAddr, origin: &ServerName, content: DeviceListUpdateContent,
) -> Result<()> {
let DeviceListUpdateContent {
user_id,
@@ -350,13 +352,13 @@ async fn handle_edu_device_list_update(
return Ok(());
}
services().users.mark_device_key_update(&user_id)?;
services.users.mark_device_key_update(&user_id)?;
Ok(())
}
async fn handle_edu_direct_to_device(
_client: &IpAddr, origin: &ServerName, content: DirectDeviceContent,
services: &Services, _client: &IpAddr, origin: &ServerName, content: DirectDeviceContent,
) -> Result<()> {
let DirectDeviceContent {
sender,
@@ -374,7 +376,7 @@ async fn handle_edu_direct_to_device(
}
// Check if this is a new transaction id
if services()
if services
.transaction_ids
.existing_txnid(&sender, None, &message_id)?
.is_some()
@@ -386,28 +388,27 @@ async fn handle_edu_direct_to_device(
for (target_device_id_maybe, event) in map {
match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => {
services().users.add_to_device_event(
services.users.add_to_device_event(
&sender,
target_user_id,
target_device_id,
&ev_type.to_string(),
event.deserialize_as().map_err(|e| {
error!("To-Device event is invalid: {event:?} {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
})?,
event
.deserialize_as()
.map_err(|e| err!(Request(InvalidParam(error!("To-Device event is invalid: {e}")))))?,
)?;
},
DeviceIdOrAllDevices::AllDevices => {
for target_device_id in services().users.all_device_ids(target_user_id) {
services().users.add_to_device_event(
for target_device_id in services.users.all_device_ids(target_user_id) {
services.users.add_to_device_event(
&sender,
target_user_id,
&target_device_id?,
&ev_type.to_string(),
event
.deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?,
.map_err(|e| err!(Request(InvalidParam("Event is invalid: {e}"))))?,
)?;
}
},
@@ -416,7 +417,7 @@ async fn handle_edu_direct_to_device(
}
// Save transaction id with empty data
services()
services
.transaction_ids
.add_txnid(&sender, None, &message_id, &[])?;
@@ -424,7 +425,7 @@ async fn handle_edu_direct_to_device(
}
async fn handle_edu_signing_key_update(
_client: &IpAddr, origin: &ServerName, content: SigningKeyUpdateContent,
services: &Services, _client: &IpAddr, origin: &ServerName, content: SigningKeyUpdateContent,
) -> Result<()> {
let SigningKeyUpdateContent {
user_id,
@@ -441,7 +442,7 @@ async fn handle_edu_signing_key_update(
}
if let Some(master_key) = master_key {
services()
services
.users
.add_cross_signing_keys(&user_id, &master_key, &self_signing_key, &None, true)?;
}
+3 -2
View File
@@ -156,8 +156,9 @@ async fn create_join_event(
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "origin is not a server name."))?;
let mutex_lock = services()
.globals
.roomid_mutex_federation
.rooms
.event_handler
.mutex_federation
.lock(room_id)
.await;
let pdu_id: Vec<u8> = services()
+3 -2
View File
@@ -152,8 +152,9 @@ async fn create_leave_event(origin: &ServerName, room_id: &RoomId, pdu: &RawJson
.await?;
let mutex_lock = services()
.globals
.roomid_mutex_federation
.rooms
.event_handler
.mutex_federation
.lock(room_id)
.await;
let pdu_id: Vec<u8> = services()
+3
View File
@@ -53,7 +53,9 @@ sha256_media = []
argon2.workspace = true
axum.workspace = true
bytes.workspace = true
checked_ops.workspace = true
chrono.workspace = true
const-str.workspace = true
either.workspace = true
figment.workspace = true
http-body-util.workspace = true
@@ -80,6 +82,7 @@ tikv-jemalloc-ctl.workspace = true
tikv-jemalloc-sys.optional = true
tikv-jemalloc-sys.workspace = true
tokio.workspace = true
tokio-metrics.workspace = true
tracing-core.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
+4 -4
View File
@@ -1,9 +1,9 @@
//! Default allocator with no special features
/// Always returns the empty string
/// Always returns None
#[must_use]
pub fn memory_stats() -> String { String::default() }
pub fn memory_stats() -> Option<String> { None }
/// Always returns the empty string
/// Always returns None
#[must_use]
pub fn memory_usage() -> String { String::default() }
pub fn memory_usage() -> Option<String> { None }
+5 -4
View File
@@ -4,9 +4,10 @@
static HMALLOC: hardened_malloc_rs::HardenedMalloc = hardened_malloc_rs::HardenedMalloc;
#[must_use]
pub fn memory_usage() -> String {
String::default() //TODO: get usage
}
//TODO: get usage
pub fn memory_usage() -> Option<string> { None }
#[must_use]
pub fn memory_stats() -> String { "Extended statistics are not available from hardened_malloc.".to_owned() }
pub fn memory_stats() -> Option<String> {
Some("Extended statistics are not available from hardened_malloc.".to_owned())
}
+22 -13
View File
@@ -10,22 +10,31 @@ use tikv_jemallocator as jemalloc;
static JEMALLOC: jemalloc::Jemalloc = jemalloc::Jemalloc;
#[must_use]
pub fn memory_usage() -> String {
pub fn memory_usage() -> Option<String> {
use mallctl::stats;
let allocated = stats::allocated::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
let active = stats::active::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
let mapped = stats::mapped::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
let metadata = stats::metadata::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
let resident = stats::resident::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
let retained = stats::retained::read().unwrap_or_default() as f64 / 1024.0 / 1024.0;
format!(
"allocated: {allocated:.2} MiB\n active: {active:.2} MiB\n mapped: {mapped:.2} MiB\n metadata: {metadata:.2} \
MiB\n resident: {resident:.2} MiB\n retained: {retained:.2} MiB\n "
)
let mibs = |input: Result<usize, mallctl::Error>| {
let input = input.unwrap_or_default();
let kibs = input / 1024;
let kibs = u32::try_from(kibs).unwrap_or_default();
let kibs = f64::from(kibs);
kibs / 1024.0
};
let allocated = mibs(stats::allocated::read());
let active = mibs(stats::active::read());
let mapped = mibs(stats::mapped::read());
let metadata = mibs(stats::metadata::read());
let resident = mibs(stats::resident::read());
let retained = mibs(stats::retained::read());
Some(format!(
"allocated: {allocated:.2} MiB\nactive: {active:.2} MiB\nmapped: {mapped:.2} MiB\nmetadata: {metadata:.2} \
MiB\nresident: {resident:.2} MiB\nretained: {retained:.2} MiB\n"
))
}
#[must_use]
pub fn memory_stats() -> String {
pub fn memory_stats() -> Option<String> {
const MAX_LENGTH: usize = 65536 - 4096;
let opts_s = "d";
@@ -42,7 +51,7 @@ pub fn memory_stats() -> String {
unsafe { ffi::malloc_stats_print(Some(malloc_stats_cb), opaque, opts_p) };
str.truncate(MAX_LENGTH);
format!("<pre><code>{str}</code></pre>")
Some(format!("<pre><code>{str}</code></pre>"))
}
extern "C" fn malloc_stats_cb(opaque: *mut c_void, msg: *const c_char) {
+162 -95
View File
@@ -1,110 +1,126 @@
#[cfg(unix)]
use std::path::Path; // not unix specific, just only for UNIX sockets stuff and *nix container checks
use figment::Figment;
use tracing::{debug, error, info, warn};
use crate::{error::Error, Config};
pub fn check(config: &Config) -> Result<(), Error> {
#[cfg(feature = "rocksdb")]
warn!(
"Note the rocksdb feature was deleted from conduwuit, sqlite was deleted and RocksDB is the only supported \
backend now. Please update your build script to remove this feature."
);
#[cfg(feature = "sha256_media")]
warn!(
"Note the sha256_media feature was deleted from conduwuit, it is now fully integrated in a \
forwards-compatible way. Please update your build script to remove this feature."
);
config.warn_deprecated();
config.warn_unknown_key();
if config.sentry && config.sentry_endpoint.is_none() {
return Err(Error::bad_config("Sentry cannot be enabled without an endpoint set"));
}
if cfg!(feature = "hardened_malloc") && cfg!(feature = "jemalloc") {
warn!("hardened_malloc and jemalloc are both enabled, this causes jemalloc to be used.");
}
if config.unix_socket_path.is_some() && !cfg!(unix) {
return Err(Error::bad_config(
"UNIX socket support is only available on *nix platforms. Please remove \"unix_socket_path\" from your \
config.",
));
}
config.get_bind_addrs().iter().for_each(|addr| {
if addr.ip().is_loopback() && cfg!(unix) {
debug!("Found loopback listening address {addr}, running checks if we're in a container.",);
#[cfg(unix)]
if Path::new("/proc/vz").exists() /* Guest */ && !Path::new("/proc/bz").exists()
/* Host */
{
error!(
"You are detected using OpenVZ with a loopback/localhost listening address of {addr}. If you are \
using OpenVZ for containers and you use NAT-based networking to communicate with the host and \
guest, this will NOT work. Please change this to \"0.0.0.0\". If this is expected, you can \
ignore.",
);
}
#[cfg(unix)]
if Path::new("/.dockerenv").exists() {
error!(
"You are detected using Docker with a loopback/localhost listening address of {addr}. If you are \
using a reverse proxy on the host and require communication to conduwuit in the Docker container \
via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is \
expected, you can ignore.",
);
}
#[cfg(unix)]
if Path::new("/run/.containerenv").exists() {
error!(
"You are detected using Podman with a loopback/localhost listening address of {addr}. If you are \
using a reverse proxy on the host and require communication to conduwuit in the Podman container \
via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". If this is \
expected, you can ignore.",
);
}
}
});
// rocksdb does not allow max_log_files to be 0
if config.rocksdb_max_log_files == 0 {
return Err(Error::bad_config(
"When using RocksDB, rocksdb_max_log_files cannot be 0. Please set a value at least 1.",
));
}
// yeah, unless the user built a debug build hopefully for local testing only
if config.server_name == "your.server.name" && !cfg!(debug_assertions) {
return Err(Error::bad_config(
"You must specify a valid server name for production usage of conduwuit.",
));
}
use super::DEPRECATED_KEYS;
use crate::{debug, debug_info, error, info, warn, Config, Err, Result};
#[allow(clippy::cognitive_complexity)]
pub fn check(config: &Config) -> Result<()> {
if cfg!(debug_assertions) {
info!("Note: conduwuit was built without optimisations (i.e. debug build)");
}
// prevents catching this in `--all-features`
if cfg!(all(feature = "rocksdb", not(feature = "sha256_media"))) {
warn!(
"Note the rocksdb feature was deleted from conduwuit. SQLite support was removed and RocksDB is the only \
supported backend now. Please update your build script to remove this feature."
);
}
// prevents catching this in `--all-features`
if cfg!(all(feature = "sha256_media", not(feature = "rocksdb"))) {
warn!(
"Note the sha256_media feature was deleted from conduwuit, it is now fully integrated in a \
forwards-compatible way. Please update your build script to remove this feature."
);
}
warn_deprecated(config);
warn_unknown_key(config);
if config.sentry && config.sentry_endpoint.is_none() {
return Err!(Config("sentry_endpoint", "Sentry cannot be enabled without an endpoint set"));
}
if cfg!(all(feature = "hardened_malloc", feature = "jemalloc")) {
warn!(
"hardened_malloc and jemalloc are both enabled, this causes jemalloc to be used. If using --all-features, \
this is harmless."
);
}
if cfg!(not(unix)) && config.unix_socket_path.is_some() {
return Err!(Config(
"unix_socket_path",
"UNIX socket support is only available on *nix platforms. Please remove 'unix_socket_path' from your \
config."
));
}
if cfg!(unix) && config.unix_socket_path.is_none() {
config.get_bind_addrs().iter().for_each(|addr| {
use std::path::Path;
if addr.ip().is_loopback() {
debug_info!("Found loopback listening address {addr}, running checks if we're in a container.");
if Path::new("/proc/vz").exists() /* Guest */ && !Path::new("/proc/bz").exists()
/* Host */
{
error!(
"You are detected using OpenVZ with a loopback/localhost listening address of {addr}. If you \
are using OpenVZ for containers and you use NAT-based networking to communicate with the \
host and guest, this will NOT work. Please change this to \"0.0.0.0\". If this is expected, \
you can ignore.",
);
}
if Path::new("/.dockerenv").exists() {
error!(
"You are detected using Docker with a loopback/localhost listening address of {addr}. If you \
are using a reverse proxy on the host and require communication to conduwuit in the Docker \
container via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". \
If this is expected, you can ignore.",
);
}
if Path::new("/run/.containerenv").exists() {
error!(
"You are detected using Podman with a loopback/localhost listening address of {addr}. If you \
are using a reverse proxy on the host and require communication to conduwuit in the Podman \
container via NAT-based networking, this will NOT work. Please change this to \"0.0.0.0\". \
If this is expected, you can ignore.",
);
}
}
});
}
// rocksdb does not allow max_log_files to be 0
if config.rocksdb_max_log_files == 0 {
return Err!(Config(
"max_log_files",
"rocksdb_max_log_files cannot be 0. Please set a value at least 1."
));
}
// yeah, unless the user built a debug build hopefully for local testing only
if cfg!(not(debug_assertions)) && config.server_name == "your.server.name" {
return Err!(Config(
"server_name",
"You must specify a valid server name for production usage of conduwuit."
));
}
// check if the user specified a registration token as `""`
if config.registration_token == Some(String::new()) {
return Err(Error::bad_config("Registration token was specified but is empty (\"\")"));
return Err!(Config(
"registration_token",
"Registration token was specified but is empty (\"\")"
));
}
if config.max_request_size < 5_120_000 {
return Err(Error::bad_config("Max request size is less than 5MB. Please increase it."));
return Err!(Config(
"max_request_size",
"Max request size is less than 5MB. Please increase it."
));
}
// check if user specified valid IP CIDR ranges on startup
for cidr in &config.ip_range_denylist {
if let Err(e) = ipaddress::IPAddress::parse(cidr) {
error!("Error parsing specified IP CIDR range from string: {e}");
return Err(Error::bad_config("Error parsing specified IP CIDR ranges from strings"));
return Err!(Config("ip_range_denylist", "Parsing specified IP CIDR range from string: {e}."));
}
}
@@ -112,13 +128,14 @@ pub fn check(config: &Config) -> Result<(), Error> {
&& !config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none()
{
return Err(Error::bad_config(
return Err!(Config(
"registration_token",
"!! You have `allow_registration` enabled without a token configured in your config which means you are \
allowing ANYONE to register on your conduwuit instance without any 2nd-step (e.g. registration token).\n
If this is not the intended behaviour, please set a registration token with the `registration_token` config option.\n
For security and safety reasons, conduwuit will shut down. If you are extra sure this is the desired behaviour you \
want, please set the following config option to true:
`yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`",
`yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`"
));
}
@@ -135,8 +152,9 @@ For security and safety reasons, conduwuit will shut down. If you are extra sure
}
if config.allow_outgoing_presence && !config.allow_local_presence {
return Err(Error::bad_config(
"Outgoing presence requires allowing local presence. Please enable \"allow_local_presence\".",
return Err!(Config(
"allow_local_presence",
"Outgoing presence requires allowing local presence. Please enable 'allow_local_presence'."
));
}
@@ -173,3 +191,52 @@ For security and safety reasons, conduwuit will shut down. If you are extra sure
Ok(())
}
/// Iterates over all the keys in the config file and warns if there is a
/// deprecated key specified
fn warn_deprecated(config: &Config) {
debug!("Checking for deprecated config keys");
let mut was_deprecated = false;
for key in config
.catchall
.keys()
.filter(|key| DEPRECATED_KEYS.iter().any(|s| s == key))
{
warn!("Config parameter \"{}\" is deprecated, ignoring.", key);
was_deprecated = true;
}
if was_deprecated {
warn!(
"Read conduwuit config documentation at https://conduwuit.puppyirl.gay/configuration.html and check your \
configuration if any new configuration parameters should be adjusted"
);
}
}
/// iterates over all the catchall keys (unknown config options) and warns
/// if there are any.
fn warn_unknown_key(config: &Config) {
debug!("Checking for unknown config keys");
for key in config
.catchall
.keys()
.filter(|key| "config".to_owned().ne(key.to_owned()) /* "config" is expected */)
{
warn!("Config parameter \"{}\" is unknown to conduwuit, ignoring.", key);
}
}
/// Checks the presence of the `address` and `unix_socket_path` keys in the
/// raw_config, exiting the process if both keys were detected.
pub(super) fn is_dual_listening(raw_config: &Figment) -> Result<()> {
let contains_address = raw_config.contains("address");
let contains_unix_socket = raw_config.contains("unix_socket_path");
if contains_address && contains_unix_socket {
return Err!(
"TOML keys \"address\" and \"unix_socket_path\" were both defined. Please specify only one option."
);
}
Ok(())
}
+401 -439
View File
@@ -1,6 +1,6 @@
use std::{
collections::BTreeMap,
fmt::{self, Write as _},
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
};
@@ -19,30 +19,15 @@ use ruma::{
api::client::discovery::discover_support::ContactRole, OwnedRoomId, OwnedServerName, OwnedUserId, RoomVersionId,
};
use serde::{de::IgnoredAny, Deserialize};
use tracing::{debug, error, warn};
use url::Url;
pub use self::check::check;
use self::proxy::ProxyConfig;
use crate::error::Error;
use crate::{error::Error, Err, Result};
pub mod check;
pub mod proxy;
#[derive(Deserialize, Clone, Debug)]
#[serde(transparent)]
struct ListeningPort {
#[serde(with = "either::serde_untagged")]
ports: Either<u16, Vec<u16>>,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(transparent)]
struct ListeningAddr {
#[serde(with = "either::serde_untagged")]
addrs: Either<IpAddr, Vec<IpAddr>>,
}
/// all the config options for conduwuit
#[derive(Clone, Debug, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
@@ -73,8 +58,8 @@ pub struct Config {
#[serde(default = "default_pdu_cache_capacity")]
pub pdu_cache_capacity: u32,
#[serde(default = "default_conduit_cache_capacity_modifier")]
pub conduit_cache_capacity_modifier: f64,
#[serde(default = "default_cache_capacity_modifier", alias = "conduit_cache_capacity_modifier")]
pub cache_capacity_modifier: f64,
#[serde(default = "default_auth_chain_cache_capacity")]
pub auth_chain_cache_capacity: u32,
#[serde(default = "default_shorteventid_cache_capacity")]
@@ -114,7 +99,7 @@ pub struct Config {
pub ip_lookup_strategy: u8,
#[serde(default = "default_max_request_size")]
pub max_request_size: u32,
pub max_request_size: usize,
#[serde(default = "default_max_fetch_prev_events")]
pub max_fetch_prev_events: u16,
@@ -181,16 +166,14 @@ pub struct Config {
#[serde(default)]
pub well_known: WellKnownConfig,
#[serde(default)]
#[cfg(feature = "perf_measurements")]
pub allow_jaeger: bool,
#[serde(default = "default_jaeger_filter")]
pub jaeger_filter: String,
#[serde(default)]
#[cfg(feature = "perf_measurements")]
pub tracing_flame: bool,
#[serde(default = "default_tracing_flame_filter")]
#[cfg(feature = "perf_measurements")]
pub tracing_flame_filter: String,
#[serde(default = "default_tracing_flame_output_path")]
#[cfg(feature = "perf_measurements")]
pub tracing_flame_output_path: String,
#[serde(default)]
pub proxy: ProxyConfig,
@@ -356,6 +339,14 @@ pub struct Config {
pub sentry_send_server_name: bool,
#[serde(default = "default_sentry_traces_sample_rate")]
pub sentry_traces_sample_rate: f32,
#[serde(default)]
pub sentry_attach_stacktrace: bool,
#[serde(default = "true_fn")]
pub sentry_send_panic: bool,
#[serde(default = "true_fn")]
pub sentry_send_error: bool,
#[serde(default = "default_sentry_filter")]
pub sentry_filter: String,
#[serde(default)]
pub tokio_console: bool,
@@ -386,8 +377,23 @@ pub struct WellKnownConfig {
pub support_mxid: Option<OwnedUserId>,
}
const DEPRECATED_KEYS: &[&str] = &[
#[derive(Deserialize, Clone, Debug)]
#[serde(transparent)]
struct ListeningPort {
#[serde(with = "either::serde_untagged")]
ports: Either<u16, Vec<u16>>,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(transparent)]
struct ListeningAddr {
#[serde(with = "either::serde_untagged")]
addrs: Either<IpAddr, Vec<IpAddr>>,
}
const DEPRECATED_KEYS: &[&str; 9] = &[
"cache_capacity",
"conduit_cache_capacity_modifier",
"max_concurrent_requests",
"well_known_client",
"well_known_server",
@@ -399,7 +405,7 @@ const DEPRECATED_KEYS: &[&str] = &[
impl Config {
/// Initialize config
pub fn new(path: Option<PathBuf>) -> Result<Self, Error> {
pub fn new(path: Option<PathBuf>) -> Result<Self> {
let raw_config = if let Some(config_file_env) = Env::var("CONDUIT_CONFIG") {
Figment::new()
.merge(Toml::file(config_file_env).nested())
@@ -422,69 +428,16 @@ impl Config {
};
let config = match raw_config.extract::<Self>() {
Err(e) => return Err(Error::BadConfig(format!("{e}"))),
Err(e) => return Err!("There was a problem with your configuration file: {e}"),
Ok(config) => config,
};
// don't start if we're listening on both UNIX sockets and TCP at same time
if Self::is_dual_listening(&raw_config) {
return Err(Error::bad_config("dual listening on UNIX and TCP sockets not allowed."));
};
check::is_dual_listening(&raw_config)?;
Ok(config)
}
/// Iterates over all the keys in the config file and warns if there is a
/// deprecated key specified
pub(crate) fn warn_deprecated(&self) {
debug!("Checking for deprecated config keys");
let mut was_deprecated = false;
for key in self
.catchall
.keys()
.filter(|key| DEPRECATED_KEYS.iter().any(|s| s == key))
{
warn!("Config parameter \"{}\" is deprecated, ignoring.", key);
was_deprecated = true;
}
if was_deprecated {
warn!(
"Read conduwuit config documentation at https://conduwuit.puppyirl.gay/configuration.html and check \
your configuration if any new configuration parameters should be adjusted"
);
}
}
/// iterates over all the catchall keys (unknown config options) and warns
/// if there are any.
pub(crate) fn warn_unknown_key(&self) {
debug!("Checking for unknown config keys");
for key in self
.catchall
.keys()
.filter(|key| "config".to_owned().ne(key.to_owned()) /* "config" is expected */)
{
warn!("Config parameter \"{}\" is unknown to conduwuit, ignoring.", key);
}
}
/// Checks the presence of the `address` and `unix_socket_path` keys in the
/// raw_config, exiting the process if both keys were detected.
fn is_dual_listening(raw_config: &Figment) -> bool {
let check_address = raw_config.find_value("address");
let check_unix_socket = raw_config.find_value("unix_socket_path");
// are the check_address and check_unix_socket keys both Ok (specified) at the
// same time?
if check_address.is_ok() && check_unix_socket.is_ok() {
error!("TOML keys \"address\" and \"unix_socket_path\" were both defined. Please specify only one option.");
return true;
}
false
}
#[must_use]
pub fn get_bind_addrs(&self) -> Vec<SocketAddr> {
let mut addrs = Vec::new();
@@ -516,361 +469,358 @@ impl Config {
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Prepare a list of config values to show
let lines = [
("Server name", self.server_name.host()),
("Database backend", &self.database_backend),
("Database path", &self.database_path.to_string_lossy()),
(
"Database backup path",
self.database_backup_path
.as_ref()
.map_or("", |path| path.to_str().unwrap_or("")),
),
("Database backups to keep", &self.database_backups_to_keep.to_string()),
("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string()),
("Cache capacity modifier", &self.conduit_cache_capacity_modifier.to_string()),
("PDU cache capacity", &self.pdu_cache_capacity.to_string()),
("Auth chain cache capacity", &self.auth_chain_cache_capacity.to_string()),
("Short eventid cache capacity", &self.shorteventid_cache_capacity.to_string()),
("Eventid short cache capacity", &self.eventidshort_cache_capacity.to_string()),
("Short statekey cache capacity", &self.shortstatekey_cache_capacity.to_string()),
("Statekey short cache capacity", &self.statekeyshort_cache_capacity.to_string()),
(
"Server visibility cache capacity",
&self.server_visibility_cache_capacity.to_string(),
),
(
"User visibility cache capacity",
&self.user_visibility_cache_capacity.to_string(),
),
("Stateinfo cache capacity", &self.stateinfo_cache_capacity.to_string()),
(
"Roomid space hierarchy cache capacity",
&self.roomid_spacehierarchy_cache_capacity.to_string(),
),
("DNS cache entry limit", &self.dns_cache_entries.to_string()),
("DNS minimum TTL", &self.dns_min_ttl.to_string()),
("DNS minimum NXDOMAIN TTL", &self.dns_min_ttl_nxdomain.to_string()),
("DNS attempts", &self.dns_attempts.to_string()),
("DNS timeout", &self.dns_timeout.to_string()),
("DNS fallback to TCP", &self.dns_tcp_fallback.to_string()),
("DNS query over TCP only", &self.query_over_tcp_only.to_string()),
("Query all nameservers", &self.query_all_nameservers.to_string()),
("Maximum request size (bytes)", &self.max_request_size.to_string()),
("Sender retry backoff limit", &self.sender_retry_backoff_limit.to_string()),
("Request connect timeout", &self.request_conn_timeout.to_string()),
("Request timeout", &self.request_timeout.to_string()),
("Request total timeout", &self.request_total_timeout.to_string()),
("Idle connections per host", &self.request_idle_per_host.to_string()),
("Request pool idle timeout", &self.request_idle_timeout.to_string()),
("Well_known connect timeout", &self.well_known_conn_timeout.to_string()),
("Well_known timeout", &self.well_known_timeout.to_string()),
("Federation timeout", &self.federation_timeout.to_string()),
("Federation pool idle per host", &self.federation_idle_per_host.to_string()),
("Federation pool idle timeout", &self.federation_idle_timeout.to_string()),
("Sender timeout", &self.sender_timeout.to_string()),
("Sender pool idle timeout", &self.sender_idle_timeout.to_string()),
("Appservice timeout", &self.appservice_timeout.to_string()),
("Appservice pool idle timeout", &self.appservice_idle_timeout.to_string()),
("Pusher pool idle timeout", &self.pusher_idle_timeout.to_string()),
("Allow registration", &self.allow_registration.to_string()),
(
"Registration token",
if self.registration_token.is_some() {
"set"
} else {
"not set (open registration!)"
},
),
(
"Allow guest registration (inherently false if allow registration is false)",
&self.allow_guest_registration.to_string(),
),
(
"Log guest registrations in admin room",
&self.log_guest_registrations.to_string(),
),
(
"Allow guests to auto join rooms",
&self.allow_guests_auto_join_rooms.to_string(),
),
("New user display name suffix", &self.new_user_displayname_suffix),
("Allow encryption", &self.allow_encryption.to_string()),
("Allow federation", &self.allow_federation.to_string()),
(
"Allow incoming federated presence requests (updates)",
&self.allow_incoming_presence.to_string(),
),
(
"Allow outgoing federated presence requests (updates)",
&self.allow_outgoing_presence.to_string(),
),
(
"Allow local presence requests (updates)",
&self.allow_local_presence.to_string(),
),
(
"Allow incoming remote read receipts",
&self.allow_incoming_read_receipts.to_string(),
),
(
"Allow outgoing remote read receipts",
&self.allow_outgoing_read_receipts.to_string(),
),
(
"Block non-admin room invites (local and remote, admins can still send and receive invites)",
&self.block_non_admin_invites.to_string(),
),
("Enable admin escape commands", &self.admin_escape_commands.to_string()),
("Allow outgoing federated typing", &self.allow_outgoing_typing.to_string()),
("Allow incoming federated typing", &self.allow_incoming_typing.to_string()),
(
"Incoming federated typing timeout",
&self.typing_federation_timeout_s.to_string(),
),
("Client typing timeout minimum", &self.typing_client_timeout_min_s.to_string()),
("Client typing timeout maxmimum", &self.typing_client_timeout_max_s.to_string()),
("Allow device name federation", &self.allow_device_name_federation.to_string()),
(
"Allow incoming profile lookup federation requests",
&self.allow_profile_lookup_federation_requests.to_string(),
),
(
"Auto deactivate banned room join attempts",
&self.auto_deactivate_banned_room_attempts.to_string(),
),
("Notification push path", &self.notification_push_path),
("Allow room creation", &self.allow_room_creation.to_string()),
(
"Allow public room directory over federation",
&self.allow_public_room_directory_over_federation.to_string(),
),
(
"Allow public room directory without authentication",
&self.allow_public_room_directory_without_auth.to_string(),
),
(
"Lockdown public room directory (only allow admins to publish)",
&self.lockdown_public_room_directory.to_string(),
),
(
"JWT secret",
match self.jwt_secret {
Some(_) => "set",
None => "not set",
},
),
(
"Trusted key servers",
&self
.trusted_servers
.iter()
.map(|server| server.host())
.join(", "),
),
(
"Query Trusted Key Servers First",
&self.query_trusted_key_servers_first.to_string(),
),
("OpenID Token TTL", &self.openid_token_ttl.to_string()),
(
"TURN username",
if self.turn_username.is_empty() {
"not set"
} else {
&self.turn_username
},
),
("TURN password", {
if self.turn_password.is_empty() {
"not set"
} else {
"set"
}
}),
("TURN secret", {
if self.turn_secret.is_empty() {
"not set"
} else {
"set"
}
}),
("Turn TTL", &self.turn_ttl.to_string()),
("Turn URIs", {
let mut lst = vec![];
for item in self.turn_uris.iter().cloned().enumerate() {
let (_, uri): (usize, String) = item;
lst.push(uri);
}
&lst.join(", ")
}),
("Auto Join Rooms", {
let mut lst = vec![];
for room in &self.auto_join_rooms {
lst.push(room);
}
&lst.into_iter().join(", ")
}),
#[cfg(feature = "zstd_compression")]
("Zstd HTTP Compression", &self.zstd_compression.to_string()),
#[cfg(feature = "gzip_compression")]
("Gzip HTTP Compression", &self.gzip_compression.to_string()),
#[cfg(feature = "brotli_compression")]
("Brotli HTTP Compression", &self.brotli_compression.to_string()),
("RocksDB database LOG level", &self.rocksdb_log_level),
("RocksDB database LOG to stderr", &self.rocksdb_log_stderr.to_string()),
("RocksDB database LOG time-to-roll", &self.rocksdb_log_time_to_roll.to_string()),
("RocksDB Max LOG Files", &self.rocksdb_max_log_files.to_string()),
(
"RocksDB database max LOG file size",
&self.rocksdb_max_log_file_size.to_string(),
),
(
"RocksDB database optimize for spinning disks",
&self.rocksdb_optimize_for_spinning_disks.to_string(),
),
("RocksDB Direct-IO", &self.rocksdb_direct_io.to_string()),
("RocksDB Parallelism Threads", &self.rocksdb_parallelism_threads.to_string()),
("RocksDB Compression Algorithm", &self.rocksdb_compression_algo),
("RocksDB Compression Level", &self.rocksdb_compression_level.to_string()),
(
"RocksDB Bottommost Compression Level",
&self.rocksdb_bottommost_compression_level.to_string(),
),
(
"RocksDB Bottommost Level Compression",
&self.rocksdb_bottommost_compression.to_string(),
),
("RocksDB Recovery Mode", &self.rocksdb_recovery_mode.to_string()),
("RocksDB Repair Mode", &self.rocksdb_repair.to_string()),
("RocksDB Read-only Mode", &self.rocksdb_read_only.to_string()),
(
"RocksDB Compaction Idle Priority",
&self.rocksdb_compaction_prio_idle.to_string(),
),
(
"RocksDB Compaction Idle IOPriority",
&self.rocksdb_compaction_ioprio_idle.to_string(),
),
("Media integrity checks on startup", &self.media_startup_check.to_string()),
("Media compatibility filesystem links", &self.media_compat_file_link.to_string()),
("Prevent Media Downloads From", {
let mut lst = vec![];
for domain in &self.prevent_media_downloads_from {
lst.push(domain.host());
}
&lst.join(", ")
}),
("Forbidden Remote Server Names (\"Global\" ACLs)", {
let mut lst = vec![];
for domain in &self.forbidden_remote_server_names {
lst.push(domain.host());
}
&lst.join(", ")
}),
("Forbidden Remote Room Directory Server Names", {
let mut lst = vec![];
for domain in &self.forbidden_remote_room_directory_server_names {
lst.push(domain.host());
}
&lst.join(", ")
}),
("Outbound Request IP Range Denylist", {
let mut lst = vec![];
for item in self.ip_range_denylist.iter().cloned().enumerate() {
let (_, ip): (usize, String) = item;
lst.push(ip);
}
&lst.join(", ")
}),
("Forbidden usernames", {
&self.forbidden_usernames.patterns().iter().join(", ")
}),
("Forbidden room aliases", {
&self.forbidden_alias_names.patterns().iter().join(", ")
}),
(
"URL preview domain contains allowlist",
&self.url_preview_domain_contains_allowlist.join(", "),
),
(
"URL preview domain explicit allowlist",
&self.url_preview_domain_explicit_allowlist.join(", "),
),
(
"URL preview domain explicit denylist",
&self.url_preview_domain_explicit_denylist.join(", "),
),
(
"URL preview URL contains allowlist",
&self.url_preview_url_contains_allowlist.join(", "),
),
("URL preview maximum spider size", &self.url_preview_max_spider_size.to_string()),
("URL preview check root domain", &self.url_preview_check_root_domain.to_string()),
(
"Allow check for updates / announcements check",
&self.allow_check_for_updates.to_string(),
),
("Enable netburst on startup", &self.startup_netburst.to_string()),
#[cfg(feature = "sentry_telemetry")]
("Sentry.io reporting and tracing", &self.sentry.to_string()),
#[cfg(feature = "sentry_telemetry")]
("Sentry.io send server_name in logs", &self.sentry_send_server_name.to_string()),
#[cfg(feature = "sentry_telemetry")]
("Sentry.io tracing sample rate", &self.sentry_traces_sample_rate.to_string()),
(
"Well-known server name",
self.well_known
.server
.as_ref()
.map_or("", |server| server.as_str()),
),
(
"Well-known client URL",
self.well_known
.client
.as_ref()
.map_or("", |url| url.as_str()),
),
(
"Well-known support email",
self.well_known
.support_email
.as_ref()
.map_or("", |str| str.as_ref()),
),
(
"Well-known support Matrix ID",
self.well_known
.support_mxid
.as_ref()
.map_or("", |mxid| mxid.as_str()),
),
(
"Well-known support role",
self.well_known
.support_role
.as_ref()
.map_or("", |role| role.as_str()),
),
(
"Well-known support page/URL",
self.well_known
.support_page
.as_ref()
.map_or("", |url| url.as_str()),
),
("Enable the tokio-console", &self.tokio_console.to_string()),
];
writeln!(f, "Active config values:\n\n").expect("wrote line to formatter stream");
let mut line = |key: &str, val: &str| {
writeln!(f, "{key}: {val}").expect("wrote line to formatter stream");
};
let mut msg: String = "Active config values:\n\n".to_owned();
line("Server name", self.server_name.host());
line("Database backend", &self.database_backend);
line("Database path", &self.database_path.to_string_lossy());
line(
"Database backup path",
self.database_backup_path
.as_ref()
.map_or("", |path| path.to_str().unwrap_or("")),
);
line("Database backups to keep", &self.database_backups_to_keep.to_string());
line("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string());
line("Cache capacity modifier", &self.cache_capacity_modifier.to_string());
line("PDU cache capacity", &self.pdu_cache_capacity.to_string());
line("Auth chain cache capacity", &self.auth_chain_cache_capacity.to_string());
line("Short eventid cache capacity", &self.shorteventid_cache_capacity.to_string());
line("Eventid short cache capacity", &self.eventidshort_cache_capacity.to_string());
line("Short statekey cache capacity", &self.shortstatekey_cache_capacity.to_string());
line("Statekey short cache capacity", &self.statekeyshort_cache_capacity.to_string());
line(
"Server visibility cache capacity",
&self.server_visibility_cache_capacity.to_string(),
);
line(
"User visibility cache capacity",
&self.user_visibility_cache_capacity.to_string(),
);
line("Stateinfo cache capacity", &self.stateinfo_cache_capacity.to_string());
line(
"Roomid space hierarchy cache capacity",
&self.roomid_spacehierarchy_cache_capacity.to_string(),
);
line("DNS cache entry limit", &self.dns_cache_entries.to_string());
line("DNS minimum TTL", &self.dns_min_ttl.to_string());
line("DNS minimum NXDOMAIN TTL", &self.dns_min_ttl_nxdomain.to_string());
line("DNS attempts", &self.dns_attempts.to_string());
line("DNS timeout", &self.dns_timeout.to_string());
line("DNS fallback to TCP", &self.dns_tcp_fallback.to_string());
line("DNS query over TCP only", &self.query_over_tcp_only.to_string());
line("Query all nameservers", &self.query_all_nameservers.to_string());
line("Maximum request size (bytes)", &self.max_request_size.to_string());
line("Sender retry backoff limit", &self.sender_retry_backoff_limit.to_string());
line("Request connect timeout", &self.request_conn_timeout.to_string());
line("Request timeout", &self.request_timeout.to_string());
line("Request total timeout", &self.request_total_timeout.to_string());
line("Idle connections per host", &self.request_idle_per_host.to_string());
line("Request pool idle timeout", &self.request_idle_timeout.to_string());
line("Well_known connect timeout", &self.well_known_conn_timeout.to_string());
line("Well_known timeout", &self.well_known_timeout.to_string());
line("Federation timeout", &self.federation_timeout.to_string());
line("Federation pool idle per host", &self.federation_idle_per_host.to_string());
line("Federation pool idle timeout", &self.federation_idle_timeout.to_string());
line("Sender timeout", &self.sender_timeout.to_string());
line("Sender pool idle timeout", &self.sender_idle_timeout.to_string());
line("Appservice timeout", &self.appservice_timeout.to_string());
line("Appservice pool idle timeout", &self.appservice_idle_timeout.to_string());
line("Pusher pool idle timeout", &self.pusher_idle_timeout.to_string());
line("Allow registration", &self.allow_registration.to_string());
line(
"Registration token",
if self.registration_token.is_some() {
"set"
} else {
"not set (open registration!)"
},
);
line(
"Allow guest registration (inherently false if allow registration is false)",
&self.allow_guest_registration.to_string(),
);
line(
"Log guest registrations in admin room",
&self.log_guest_registrations.to_string(),
);
line(
"Allow guests to auto join rooms",
&self.allow_guests_auto_join_rooms.to_string(),
);
line("New user display name suffix", &self.new_user_displayname_suffix);
line("Allow encryption", &self.allow_encryption.to_string());
line("Allow federation", &self.allow_federation.to_string());
line(
"Allow incoming federated presence requests (updates)",
&self.allow_incoming_presence.to_string(),
);
line(
"Allow outgoing federated presence requests (updates)",
&self.allow_outgoing_presence.to_string(),
);
line(
"Allow local presence requests (updates)",
&self.allow_local_presence.to_string(),
);
line(
"Allow incoming remote read receipts",
&self.allow_incoming_read_receipts.to_string(),
);
line(
"Allow outgoing remote read receipts",
&self.allow_outgoing_read_receipts.to_string(),
);
line(
"Block non-admin room invites (local and remote, admins can still send and receive invites)",
&self.block_non_admin_invites.to_string(),
);
line("Enable admin escape commands", &self.admin_escape_commands.to_string());
line("Allow outgoing federated typing", &self.allow_outgoing_typing.to_string());
line("Allow incoming federated typing", &self.allow_incoming_typing.to_string());
line(
"Incoming federated typing timeout",
&self.typing_federation_timeout_s.to_string(),
);
line("Client typing timeout minimum", &self.typing_client_timeout_min_s.to_string());
line("Client typing timeout maxmimum", &self.typing_client_timeout_max_s.to_string());
line("Allow device name federation", &self.allow_device_name_federation.to_string());
line(
"Allow incoming profile lookup federation requests",
&self.allow_profile_lookup_federation_requests.to_string(),
);
line(
"Auto deactivate banned room join attempts",
&self.auto_deactivate_banned_room_attempts.to_string(),
);
line("Notification push path", &self.notification_push_path);
line("Allow room creation", &self.allow_room_creation.to_string());
line(
"Allow public room directory over federation",
&self.allow_public_room_directory_over_federation.to_string(),
);
line(
"Allow public room directory without authentication",
&self.allow_public_room_directory_without_auth.to_string(),
);
line(
"Lockdown public room directory (only allow admins to publish)",
&self.lockdown_public_room_directory.to_string(),
);
line(
"JWT secret",
match self.jwt_secret {
Some(_) => "set",
None => "not set",
},
);
line(
"Trusted key servers",
&self
.trusted_servers
.iter()
.map(|server| server.host())
.join(", "),
);
line(
"Query Trusted Key Servers First",
&self.query_trusted_key_servers_first.to_string(),
);
line("OpenID Token TTL", &self.openid_token_ttl.to_string());
line(
"TURN username",
if self.turn_username.is_empty() {
"not set"
} else {
&self.turn_username
},
);
line("TURN password", {
if self.turn_password.is_empty() {
"not set"
} else {
"set"
}
});
line("TURN secret", {
if self.turn_secret.is_empty() {
"not set"
} else {
"set"
}
});
line("Turn TTL", &self.turn_ttl.to_string());
line("Turn URIs", {
let mut lst = vec![];
for item in self.turn_uris.iter().cloned().enumerate() {
let (_, uri): (usize, String) = item;
lst.push(uri);
}
&lst.join(", ")
});
line("Auto Join Rooms", {
let mut lst = vec![];
for room in &self.auto_join_rooms {
lst.push(room);
}
&lst.into_iter().join(", ")
});
line("Zstd HTTP Compression", &self.zstd_compression.to_string());
line("Gzip HTTP Compression", &self.gzip_compression.to_string());
line("Brotli HTTP Compression", &self.brotli_compression.to_string());
line("RocksDB database LOG level", &self.rocksdb_log_level);
line("RocksDB database LOG to stderr", &self.rocksdb_log_stderr.to_string());
line("RocksDB database LOG time-to-roll", &self.rocksdb_log_time_to_roll.to_string());
line("RocksDB Max LOG Files", &self.rocksdb_max_log_files.to_string());
line(
"RocksDB database max LOG file size",
&self.rocksdb_max_log_file_size.to_string(),
);
line(
"RocksDB database optimize for spinning disks",
&self.rocksdb_optimize_for_spinning_disks.to_string(),
);
line("RocksDB Direct-IO", &self.rocksdb_direct_io.to_string());
line("RocksDB Parallelism Threads", &self.rocksdb_parallelism_threads.to_string());
line("RocksDB Compression Algorithm", &self.rocksdb_compression_algo);
line("RocksDB Compression Level", &self.rocksdb_compression_level.to_string());
line(
"RocksDB Bottommost Compression Level",
&self.rocksdb_bottommost_compression_level.to_string(),
);
line(
"RocksDB Bottommost Level Compression",
&self.rocksdb_bottommost_compression.to_string(),
);
line("RocksDB Recovery Mode", &self.rocksdb_recovery_mode.to_string());
line("RocksDB Repair Mode", &self.rocksdb_repair.to_string());
line("RocksDB Read-only Mode", &self.rocksdb_read_only.to_string());
line(
"RocksDB Compaction Idle Priority",
&self.rocksdb_compaction_prio_idle.to_string(),
);
line(
"RocksDB Compaction Idle IOPriority",
&self.rocksdb_compaction_ioprio_idle.to_string(),
);
line("Media integrity checks on startup", &self.media_startup_check.to_string());
line("Media compatibility filesystem links", &self.media_compat_file_link.to_string());
line("Prevent Media Downloads From", {
let mut lst = vec![];
for domain in &self.prevent_media_downloads_from {
lst.push(domain.host());
}
&lst.join(", ")
});
line("Forbidden Remote Server Names (\"Global\" ACLs)", {
let mut lst = vec![];
for domain in &self.forbidden_remote_server_names {
lst.push(domain.host());
}
&lst.join(", ")
});
line("Forbidden Remote Room Directory Server Names", {
let mut lst = vec![];
for domain in &self.forbidden_remote_room_directory_server_names {
lst.push(domain.host());
}
&lst.join(", ")
});
line("Outbound Request IP Range Denylist", {
let mut lst = vec![];
for item in self.ip_range_denylist.iter().cloned().enumerate() {
let (_, ip): (usize, String) = item;
lst.push(ip);
}
&lst.join(", ")
});
line("Forbidden usernames", {
&self.forbidden_usernames.patterns().iter().join(", ")
});
line("Forbidden room aliases", {
&self.forbidden_alias_names.patterns().iter().join(", ")
});
line(
"URL preview domain contains allowlist",
&self.url_preview_domain_contains_allowlist.join(", "),
);
line(
"URL preview domain explicit allowlist",
&self.url_preview_domain_explicit_allowlist.join(", "),
);
line(
"URL preview domain explicit denylist",
&self.url_preview_domain_explicit_denylist.join(", "),
);
line(
"URL preview URL contains allowlist",
&self.url_preview_url_contains_allowlist.join(", "),
);
line("URL preview maximum spider size", &self.url_preview_max_spider_size.to_string());
line("URL preview check root domain", &self.url_preview_check_root_domain.to_string());
line(
"Allow check for updates / announcements check",
&self.allow_check_for_updates.to_string(),
);
line("Enable netburst on startup", &self.startup_netburst.to_string());
#[cfg(feature = "sentry_telemetry")]
line("Sentry.io reporting and tracing", &self.sentry.to_string());
#[cfg(feature = "sentry_telemetry")]
line("Sentry.io send server_name in logs", &self.sentry_send_server_name.to_string());
#[cfg(feature = "sentry_telemetry")]
line("Sentry.io tracing sample rate", &self.sentry_traces_sample_rate.to_string());
line("Sentry.io attach stacktrace", &self.sentry_attach_stacktrace.to_string());
line("Sentry.io send panics", &self.sentry_send_panic.to_string());
line("Sentry.io send errors", &self.sentry_send_error.to_string());
line("Sentry.io tracing filter", &self.sentry_filter);
line(
"Well-known server name",
self.well_known
.server
.as_ref()
.map_or("", |server| server.as_str()),
);
line(
"Well-known client URL",
self.well_known
.client
.as_ref()
.map_or("", |url| url.as_str()),
);
line(
"Well-known support email",
self.well_known
.support_email
.as_ref()
.map_or("", |str| str.as_ref()),
);
line(
"Well-known support Matrix ID",
self.well_known
.support_mxid
.as_ref()
.map_or("", |mxid| mxid.as_str()),
);
line(
"Well-known support role",
self.well_known
.support_role
.as_ref()
.map_or("", |role| role.as_str()),
);
line(
"Well-known support page/URL",
self.well_known
.support_page
.as_ref()
.map_or("", |url| url.as_str()),
);
line("Enable the tokio-console", &self.tokio_console.to_string());
for line in lines.into_iter().enumerate() {
writeln!(msg, "{}: {}", line.1 .0, line.1 .1).expect("should be able to write to string buffer");
}
write!(f, "{msg}")
Ok(())
}
}
@@ -898,7 +848,7 @@ fn default_db_cache_capacity_mb() -> f64 { 256.0 }
fn default_pdu_cache_capacity() -> u32 { 150_000 }
fn default_conduit_cache_capacity_modifier() -> f64 { 1.0 }
fn default_cache_capacity_modifier() -> f64 { 1.0 }
fn default_auth_chain_cache_capacity() -> u32 { 100_000 }
@@ -930,7 +880,7 @@ fn default_dns_timeout() -> u64 { 10 }
fn default_ip_lookup_strategy() -> u8 { 5 }
fn default_max_request_size() -> u32 {
fn default_max_request_size() -> usize {
20 * 1024 * 1024 // Default to 20 MB
}
@@ -968,10 +918,20 @@ fn default_pusher_idle_timeout() -> u64 { 15 }
fn default_max_fetch_prev_events() -> u16 { 100_u16 }
#[cfg(feature = "perf_measurements")]
fn default_tracing_flame_filter() -> String { "trace,h2=off".to_owned() }
fn default_tracing_flame_filter() -> String {
cfg!(debug_assertions)
.then_some("trace,h2=off")
.unwrap_or("info")
.to_owned()
}
fn default_jaeger_filter() -> String {
cfg!(debug_assertions)
.then_some("trace,h2=off")
.unwrap_or("info")
.to_owned()
}
#[cfg(feature = "perf_measurements")]
fn default_tracing_flame_output_path() -> String { "./tracing.folded".to_owned() }
fn default_trusted_servers() -> Vec<OwnedServerName> { vec![OwnedServerName::try_from("matrix.org").unwrap()] }
@@ -1070,4 +1030,6 @@ fn default_sentry_endpoint() -> Option<Url> {
fn default_sentry_traces_sample_rate() -> f32 { 0.15 }
fn default_sentry_filter() -> String { "info".to_owned() }
fn default_startup_netburst_keep() -> i64 { 50 }
+1
View File
@@ -127,6 +127,7 @@ impl WildCardedDomain {
impl std::str::FromStr for WildCardedDomain {
type Err = std::convert::Infallible;
#[allow(clippy::string_slice)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
// maybe do some domain validation?
Ok(if s.starts_with("*.") {
+9 -8
View File
@@ -1,6 +1,4 @@
#![allow(dead_code)] // this is a developer's toolbox
use std::panic;
use std::{any::Any, panic};
/// Export all of the ancillary tools from here as well.
pub use crate::utils::debug::*;
@@ -14,9 +12,9 @@ pub use crate::utils::debug::*;
macro_rules! debug_event {
( $level:expr, $($x:tt)+ ) => {
if cfg!(debug_assertions) && cfg!(not(feature = "dev_release_log_level")) {
::tracing::event!( $level, $($x)+ );
::tracing::event!( $level, $($x)+ )
} else {
::tracing::debug!( $($x)+ );
::tracing::debug!( $($x)+ )
}
}
}
@@ -27,7 +25,7 @@ macro_rules! debug_event {
#[macro_export]
macro_rules! debug_error {
( $($x:tt)+ ) => {
$crate::debug_event!(::tracing::Level::ERROR, $($x)+ );
$crate::debug_event!(::tracing::Level::ERROR, $($x)+ )
}
}
@@ -37,7 +35,7 @@ macro_rules! debug_error {
#[macro_export]
macro_rules! debug_warn {
( $($x:tt)+ ) => {
$crate::debug_event!(::tracing::Level::WARN, $($x)+ );
$crate::debug_event!(::tracing::Level::WARN, $($x)+ )
}
}
@@ -47,7 +45,7 @@ macro_rules! debug_warn {
#[macro_export]
macro_rules! debug_info {
( $($x:tt)+ ) => {
$crate::debug_event!(::tracing::Level::INFO, $($x)+ );
$crate::debug_event!(::tracing::Level::INFO, $($x)+ )
}
}
@@ -79,3 +77,6 @@ pub fn trap() {
std::arch::asm!("int3");
}
}
#[must_use]
pub fn panic_str(p: &Box<dyn Any + Send>) -> &'static str { p.downcast_ref::<&str>().copied().unwrap_or_default() }
-210
View File
@@ -1,210 +0,0 @@
use std::{convert::Infallible, fmt};
use axum::response::{IntoResponse, Response};
use bytes::BytesMut;
use http::StatusCode;
use http_body_util::Full;
use ruma::{
api::{
client::{
error::ErrorKind::{
Forbidden, GuestAccessForbidden, LimitExceeded, MissingToken, NotFound, ThreepidAuthFailed,
ThreepidDenied, TooLarge, Unauthorized, Unknown, UnknownToken, Unrecognized, UserDeactivated,
WrongRoomKeysVersion,
},
uiaa::{UiaaInfo, UiaaResponse},
},
OutgoingResponse,
},
OwnedServerName,
};
use thiserror::Error;
use tracing::error;
#[derive(Error)]
pub enum Error {
// std
#[error("{0}")]
Fmt(#[from] fmt::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("{0}")]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error("{0}")]
TryFromSliceError(#[from] std::array::TryFromSliceError),
// third-party
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("Tracing filter error: {0}")]
TracingFilter(#[from] tracing_subscriber::filter::ParseError),
#[error("Image error: {0}")]
Image(#[from] image::error::ImageError),
#[error("Request error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("{0}")]
Extension(#[from] axum::extract::rejection::ExtensionRejection),
#[error("{0}")]
Path(#[from] axum::extract::rejection::PathRejection),
// ruma
#[error("{0}")]
Mxid(#[from] ruma::IdParseError),
#[error("{0}: {1}")]
BadRequest(ruma::api::client::error::ErrorKind, &'static str),
#[error("from {0}: {1}")]
Redaction(OwnedServerName, ruma::canonical_json::RedactionError),
#[error("Remote server {0} responded with: {1}")]
Federation(OwnedServerName, ruma::api::client::error::Error),
#[error("{0} in {1}")]
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
// conduwuit
#[error("There was a problem with your configuration: {0}")]
BadConfig(String),
#[error("{0}")]
BadDatabase(&'static str),
#[error("{0}")]
Database(String),
#[error("{0}")]
BadServerResponse(&'static str),
#[error("{0}")]
Conflict(&'static str), // This is only needed for when a room alias already exists
#[error("uiaa")]
Uiaa(UiaaInfo),
// unique / untyped
#[error("{0}")]
Err(String),
}
impl Error {
pub fn bad_database(message: &'static str) -> Self {
error!("BadDatabase: {}", message);
Self::BadDatabase(message)
}
pub fn bad_config(message: &str) -> Self {
error!("BadConfig: {}", message);
Self::BadConfig(message.to_owned())
}
/// Returns the Matrix error code / error kind
#[inline]
pub fn error_code(&self) -> ruma::api::client::error::ErrorKind {
if let Self::Federation(_, error) = self {
return error.error_kind().unwrap_or_else(|| &Unknown).clone();
}
match self {
Self::BadRequest(kind, _) => kind.clone(),
_ => Unknown,
}
}
/// Sanitizes public-facing errors that can leak sensitive information.
pub fn sanitized_error(&self) -> String {
match self {
Self::Database {
..
} => String::from("Database error occurred."),
Self::Io {
..
} => String::from("I/O error occurred."),
_ => self.to_string(),
}
}
}
impl From<Infallible> for Error {
fn from(i: Infallible) -> Self { match i {} }
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
}
#[derive(Clone)]
pub struct RumaResponse<T>(pub T);
impl<T> From<T> for RumaResponse<T> {
fn from(t: T) -> Self { Self(t) }
}
impl From<Error> for RumaResponse<UiaaResponse> {
fn from(t: Error) -> Self { t.to_response() }
}
impl Error {
pub fn to_response(&self) -> RumaResponse<UiaaResponse> {
use ruma::api::client::error::{Error as RumaError, ErrorBody};
if let Self::Uiaa(uiaainfo) = self {
return RumaResponse(UiaaResponse::AuthResponse(uiaainfo.clone()));
}
if let Self::Federation(origin, error) = self {
let mut error = error.clone();
error.body = ErrorBody::Standard {
kind: error.error_kind().unwrap_or_else(|| &Unknown).clone(),
message: format!("Answer from {origin}: {error}"),
};
return RumaResponse(UiaaResponse::MatrixError(error));
}
let message = format!("{self}");
let (kind, status_code) = match self {
Self::BadRequest(kind, _) => (
kind.clone(),
match kind {
WrongRoomKeysVersion {
..
}
| Forbidden {
..
}
| GuestAccessForbidden
| ThreepidAuthFailed
| UserDeactivated
| ThreepidDenied => StatusCode::FORBIDDEN,
Unauthorized
| UnknownToken {
..
}
| MissingToken => StatusCode::UNAUTHORIZED,
NotFound | Unrecognized => StatusCode::NOT_FOUND,
LimitExceeded {
..
} => StatusCode::TOO_MANY_REQUESTS,
TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
_ => StatusCode::BAD_REQUEST,
},
),
Self::Conflict(_) => (Unknown, StatusCode::CONFLICT),
_ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR),
};
RumaResponse(UiaaResponse::MatrixError(RumaError {
body: ErrorBody::Standard {
kind,
message,
},
status_code,
}))
}
}
impl ::axum::response::IntoResponse for Error {
fn into_response(self) -> ::axum::response::Response { self.to_response().into_response() }
}
impl<T: OutgoingResponse> IntoResponse for RumaResponse<T> {
fn into_response(self) -> Response {
match self.0.try_into_http_response::<BytesMut>() {
Ok(res) => res.map(BytesMut::freeze).map(Full::new).into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
}
+100
View File
@@ -0,0 +1,100 @@
//! Error construction macros
//!
//! These are specialized macros specific to this project's patterns for
//! throwing Errors; they make Error construction succinct and reduce clutter.
//! They are developed from folding existing patterns into the macro while
//! fixing several anti-patterns in the codebase.
//!
//! - The primary macros `Err!` and `err!` are provided. `Err!` simply wraps
//! `err!` in the Result variant to reduce `Err(err!(...))` boilerplate, thus
//! `err!` can be used in any case.
//!
//! 1. The macro makes the general Error construction easy: `return
//! Err!("something went wrong")` replaces the prior `return
//! Err(Error::Err("something went wrong".to_owned()))`.
//!
//! 2. The macro integrates format strings automatically: `return
//! Err!("something bad: {msg}")` replaces the prior `return
//! Err(Error::Err(format!("something bad: {msg}")))`.
//!
//! 3. The macro scopes variants of Error: `return Err!(Database("problem with
//! bad database."))` replaces the prior `return Err(Error::Database("problem
//! with bad database."))`.
//!
//! 4. The macro matches and scopes some special-case sub-variants, for example
//! with ruma ErrorKind: `return Err!(Request(MissingToken("you must provide
//! an access token")))`.
//!
//! 5. The macro fixes the anti-pattern of repeating messages in an error! log
//! and then again in an Error construction, often slightly different due to
//! the Error variant not supporting a format string. Instead `return
//! Err(Database(error!("problem with db: {msg}")))` logs the error at the
//! callsite and then returns the error with the same string. Caller has the
//! option of replacing `error!` with `debug_error!`.
#[macro_export]
macro_rules! Err {
($($args:tt)*) => {
Err($crate::err!($($args)*))
};
}
#[macro_export]
macro_rules! err {
(Config($item:literal, $($args:expr),*)) => {{
$crate::error!(config = %$item, $($args),*);
$crate::error::Error::Config($item, $crate::format_maybe!($($args),*))
}};
(Request(Forbidden($level:ident!($($args:expr),*)))) => {{
$crate::$level!($($args),*);
$crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::forbidden(),
$crate::format_maybe!($($args),*),
::http::StatusCode::BAD_REQUEST
)
}};
(Request(Forbidden($($args:expr),*))) => {
$crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::forbidden(),
$crate::format_maybe!($($args),*),
::http::StatusCode::BAD_REQUEST
)
};
(Request($variant:ident($level:ident!($($args:expr),*)))) => {{
$crate::$level!($($args),*);
$crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::$variant,
$crate::format_maybe!($($args),*),
::http::StatusCode::BAD_REQUEST
)
}};
(Request($variant:ident($($args:expr),*))) => {
$crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::$variant,
$crate::format_maybe!($($args),*),
::http::StatusCode::BAD_REQUEST
)
};
($variant:ident($level:ident!($($args:expr),*))) => {{
$crate::$level!($($args),*);
$crate::error::Error::$variant($crate::format_maybe!($($args),*))
}};
($variant:ident($($args:expr),*)) => {
$crate::error::Error::$variant($crate::format_maybe!($($args),*))
};
($level:ident!($($args:expr),*)) => {{
$crate::$level!($($args),*);
$crate::error::Error::Err($crate::format_maybe!($($args),*))
}};
($($args:expr),*) => {
$crate::error::Error::Err($crate::format_maybe!($($args),*))
};
}
+74
View File
@@ -0,0 +1,74 @@
use std::{convert::Infallible, fmt};
use super::Error;
use crate::{debug_error, error};
#[inline]
pub fn else_log<T, E>(error: E) -> Result<T, Infallible>
where
T: Default,
Error: From<E>,
{
Ok(default_log(error))
}
#[inline]
pub fn else_debug_log<T, E>(error: E) -> Result<T, Infallible>
where
T: Default,
Error: From<E>,
{
Ok(default_debug_log(error))
}
#[inline]
pub fn default_log<T, E>(error: E) -> T
where
T: Default,
Error: From<E>,
{
let error = Error::from(error);
inspect_log(&error);
T::default()
}
#[inline]
pub fn default_debug_log<T, E>(error: E) -> T
where
T: Default,
Error: From<E>,
{
let error = Error::from(error);
inspect_debug_log(&error);
T::default()
}
#[inline]
pub fn map_log<E>(error: E) -> Error
where
Error: From<E>,
{
let error = Error::from(error);
inspect_log(&error);
error
}
#[inline]
pub fn map_debug_log<E>(error: E) -> Error
where
Error: From<E>,
{
let error = Error::from(error);
inspect_debug_log(&error);
error
}
#[inline]
pub fn inspect_log<E: fmt::Display>(error: &E) {
error!("{error}");
}
#[inline]
pub fn inspect_debug_log<E: fmt::Debug>(error: &E) {
debug_error!("{error:?}");
}
+156
View File
@@ -0,0 +1,156 @@
mod err;
mod log;
mod panic;
mod response;
use std::{any::Any, borrow::Cow, convert::Infallible, fmt};
pub use log::*;
use crate::error;
#[derive(thiserror::Error)]
pub enum Error {
#[error("PANIC!")]
PanicAny(Box<dyn Any + Send>),
#[error("PANIC! {0}")]
Panic(&'static str, Box<dyn Any + Send + 'static>),
// std
#[error("{0}")]
Fmt(#[from] fmt::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("{0}")]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error("{0}")]
TryFromSliceError(#[from] std::array::TryFromSliceError),
#[error("{0}")]
TryFromIntError(#[from] std::num::TryFromIntError),
#[error("{0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("{0}")]
ParseFloatError(#[from] std::num::ParseFloatError),
// third-party
#[error("Join error: {0}")]
JoinError(#[from] tokio::task::JoinError),
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("Tracing filter error: {0}")]
TracingFilter(#[from] tracing_subscriber::filter::ParseError),
#[error("Tracing reload error: {0}")]
TracingReload(#[from] tracing_subscriber::reload::Error),
#[error("Image error: {0}")]
Image(#[from] image::error::ImageError),
#[error("Request error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("{0}")]
Extension(#[from] axum::extract::rejection::ExtensionRejection),
#[error("{0}")]
Path(#[from] axum::extract::rejection::PathRejection),
#[error("{0}")]
Http(#[from] http::Error),
#[error("{0}")]
HttpHeader(#[from] http::header::InvalidHeaderValue),
// ruma
#[error("{0}")]
IntoHttpError(#[from] ruma::api::error::IntoHttpError),
#[error("{0}")]
RumaError(#[from] ruma::api::client::error::Error),
#[error("uiaa")]
Uiaa(ruma::api::client::uiaa::UiaaInfo),
#[error("{0}")]
Mxid(#[from] ruma::IdParseError),
#[error("{0}: {1}")]
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove
#[error("{0}: {1}")]
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error("from {0}: {1}")]
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
#[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error),
#[error("{0} in {1}")]
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
// conduwuit
#[error("Arithmetic operation failed: {0}")]
Arithmetic(Cow<'static, str>),
#[error("There was a problem with the '{0}' directive in your configuration: {1}")]
Config(&'static str, Cow<'static, str>),
#[error("{0}")]
Database(Cow<'static, str>),
#[error("{0}")]
BadServerResponse(&'static str),
#[error("{0}")]
Conflict(&'static str), // This is only needed for when a room alias already exists
// unique / untyped
#[error("{0}")]
Err(Cow<'static, str>),
}
impl Error {
pub fn bad_database(message: &'static str) -> Self { crate::err!(Database(error!("{message}"))) }
/// Sanitizes public-facing errors that can leak sensitive information.
pub fn sanitized_string(&self) -> String {
match self {
Self::Database(..) => String::from("Database error occurred."),
Self::Io(..) => String::from("I/O error occurred."),
_ => self.to_string(),
}
}
pub fn message(&self) -> String {
match self {
Self::Federation(ref origin, ref error) => format!("Answer from {origin}: {error}"),
Self::RumaError(ref error) => response::ruma_error_message(error),
_ => format!("{self}"),
}
}
/// Returns the Matrix error code / error kind
#[inline]
pub fn kind(&self) -> ruma::api::client::error::ErrorKind {
use ruma::api::client::error::ErrorKind::Unknown;
match self {
Self::Federation(_, error) => response::ruma_error_kind(error).clone(),
Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
_ => Unknown,
}
}
pub fn status_code(&self) -> http::StatusCode {
match self {
Self::Federation(_, ref error) | Self::RumaError(ref error) => error.status_code,
Self::Request(ref kind, _, code) => response::status_code(kind, *code),
Self::BadRequest(ref kind, ..) => response::bad_request_code(kind),
Self::Conflict(_) => http::StatusCode::CONFLICT,
_ => http::StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
}
#[allow(clippy::fallible_impl_from)]
impl From<Infallible> for Error {
#[cold]
#[inline(never)]
fn from(_e: Infallible) -> Self {
panic!("infallible error should never exist");
}
}
#[cold]
#[inline(never)]
pub fn infallible(_e: &Infallible) {
panic!("infallible error should never exist");
}
+41
View File
@@ -0,0 +1,41 @@
use std::{
any::Any,
panic::{panic_any, RefUnwindSafe, UnwindSafe},
};
use super::Error;
use crate::debug;
impl UnwindSafe for Error {}
impl RefUnwindSafe for Error {}
impl Error {
pub fn panic(self) -> ! { panic_any(self.into_panic()) }
#[must_use]
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) }
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
match self {
Self::Panic(_, e) | Self::PanicAny(e) => e,
Self::JoinError(e) => e.into_panic(),
_ => Box::new(self),
}
}
/// Get the panic message string.
pub fn panic_str(self) -> Option<&'static str> {
self.is_panic()
.then_some(debug::panic_str(&self.into_panic()))
}
/// Check if the Error is trafficking a panic object.
#[inline]
pub fn is_panic(&self) -> bool {
match &self {
Self::Panic(..) | Self::PanicAny(..) => true,
Self::JoinError(e) => e.is_panic(),
_ => false,
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use bytes::BytesMut;
use http::StatusCode;
use http_body_util::Full;
use ruma::api::{
client::{
error::{ErrorBody, ErrorKind},
uiaa::UiaaResponse,
},
OutgoingResponse,
};
use super::Error;
use crate::error;
impl axum::response::IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
let response: UiaaResponse = self.into();
response
.try_into_http_response::<BytesMut>()
.inspect_err(|e| error!("error response error: {e}"))
.map_or_else(
|_| StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|r| r.map(BytesMut::freeze).map(Full::new).into_response(),
)
}
}
impl From<Error> for UiaaResponse {
fn from(error: Error) -> Self {
if let Error::Uiaa(uiaainfo) = error {
return Self::AuthResponse(uiaainfo);
}
let body = ErrorBody::Standard {
kind: error.kind(),
message: error.message(),
};
Self::MatrixError(ruma::api::client::error::Error {
status_code: error.status_code(),
body,
})
}
}
pub(super) fn status_code(kind: &ErrorKind, hint: StatusCode) -> StatusCode {
if hint == StatusCode::BAD_REQUEST {
bad_request_code(kind)
} else {
hint
}
}
pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
use ErrorKind::*;
match kind {
// 429
LimitExceeded {
..
} => StatusCode::TOO_MANY_REQUESTS,
// 413
TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
// 405
Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
// 404
NotFound => StatusCode::NOT_FOUND,
// 403
GuestAccessForbidden
| ThreepidAuthFailed
| UserDeactivated
| ThreepidDenied
| WrongRoomKeysVersion {
..
}
| Forbidden {
..
} => StatusCode::FORBIDDEN,
// 401
UnknownToken {
..
}
| MissingToken
| Unauthorized => StatusCode::UNAUTHORIZED,
// 400
_ => StatusCode::BAD_REQUEST,
}
}
pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> String {
if let ErrorBody::Standard {
message,
..
} = &error.body
{
return message.to_string();
}
format!("{error}")
}
pub(super) fn ruma_error_kind(e: &ruma::api::client::error::Error) -> &ErrorKind {
e.error_kind().unwrap_or(&ErrorKind::Unknown)
}
+5 -5
View File
@@ -29,25 +29,25 @@ pub struct Log {
#[macro_export]
macro_rules! error {
( $($x:tt)+ ) => { ::tracing::error!( $($x)+ ); }
( $($x:tt)+ ) => { ::tracing::error!( $($x)+ ) }
}
#[macro_export]
macro_rules! warn {
( $($x:tt)+ ) => { ::tracing::warn!( $($x)+ ); }
( $($x:tt)+ ) => { ::tracing::warn!( $($x)+ ) }
}
#[macro_export]
macro_rules! info {
( $($x:tt)+ ) => { ::tracing::info!( $($x)+ ); }
( $($x:tt)+ ) => { ::tracing::info!( $($x)+ ) }
}
#[macro_export]
macro_rules! debug {
( $($x:tt)+ ) => { ::tracing::debug!( $($x)+ ); }
( $($x:tt)+ ) => { ::tracing::debug!( $($x)+ ) }
}
#[macro_export]
macro_rules! trace {
( $($x:tt)+ ) => { ::tracing::trace!( $($x)+ ); }
( $($x:tt)+ ) => { ::tracing::trace!( $($x)+ ) }
}
+46 -20
View File
@@ -1,7 +1,12 @@
use std::sync::Arc;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tracing_subscriber::{reload, EnvFilter};
use crate::{error, Result};
/// We need to store a reload::Handle value, but can't name it's type explicitly
/// because the S type parameter depends on the subscriber's previous layers. In
/// our case, this includes unnameable 'impl Trait' types.
@@ -17,39 +22,60 @@ use tracing_subscriber::{reload, EnvFilter};
///
/// [1]: <https://github.com/tokio-rs/tracing/pull/1035/commits/8a87ea52425098d3ef8f56d92358c2f6c144a28f>
pub trait ReloadHandle<L> {
fn current(&self) -> Option<L>;
fn reload(&self, new_value: L) -> Result<(), reload::Error>;
}
impl<L, S> ReloadHandle<L> for reload::Handle<L, S> {
impl<L: Clone, S> ReloadHandle<L> for reload::Handle<L, S> {
fn current(&self) -> Option<L> { Self::clone_current(self) }
fn reload(&self, new_value: L) -> Result<(), reload::Error> { Self::reload(self, new_value) }
}
struct LogLevelReloadHandlesInner {
handles: Vec<Box<dyn ReloadHandle<EnvFilter> + Send + Sync>>,
}
/// Wrapper to allow reloading the filter on several several
/// [`tracing_subscriber::reload::Handle`]s at once, with the same value.
#[derive(Clone)]
pub struct LogLevelReloadHandles {
inner: Arc<LogLevelReloadHandlesInner>,
handles: Arc<Mutex<HandleMap>>,
}
type HandleMap = HashMap<String, Handle>;
type Handle = Box<dyn ReloadHandle<EnvFilter> + Send + Sync>;
impl LogLevelReloadHandles {
#[must_use]
pub fn new(handles: Vec<Box<dyn ReloadHandle<EnvFilter> + Send + Sync>>) -> Self {
Self {
inner: Arc::new(LogLevelReloadHandlesInner {
handles,
}),
}
pub fn add(&self, name: &str, handle: Handle) {
self.handles
.lock()
.expect("locked")
.insert(name.into(), handle);
}
pub fn reload(&self, new_value: &EnvFilter) -> Result<(), reload::Error> {
for handle in &self.inner.handles {
handle.reload(new_value.clone())?;
}
pub fn reload(&self, new_value: &EnvFilter, names: Option<&[&str]>) -> Result<()> {
self.handles
.lock()
.expect("locked")
.iter()
.filter(|(name, _)| names.map_or(false, |names| names.contains(&name.as_str())))
.for_each(|(_, handle)| {
_ = handle.reload(new_value.clone()).or_else(error::else_log);
});
Ok(())
}
#[must_use]
pub fn current(&self, name: &str) -> Option<EnvFilter> {
self.handles
.lock()
.expect("locked")
.get(name)
.map(|handle| handle.current())?
}
}
impl Default for LogLevelReloadHandles {
fn default() -> Self {
Self {
handles: Arc::new(HandleMap::new().into()),
}
}
}
+10 -5
View File
@@ -10,16 +10,21 @@ pub struct Suppress {
impl Suppress {
pub fn new(server: &Arc<Server>) -> Self {
let handle = "console";
let config = &server.config.log;
Self::from_filters(server, EnvFilter::try_new(config).unwrap_or_default(), &EnvFilter::default())
}
let suppress = EnvFilter::default();
let restore = server
.log
.reload
.current(handle)
.unwrap_or_else(|| EnvFilter::try_new(config).unwrap_or_default());
fn from_filters(server: &Arc<Server>, restore: EnvFilter, suppress: &EnvFilter) -> Self {
server
.log
.reload
.reload(suppress)
.reload(&suppress, Some(&[handle]))
.expect("log filter reloaded");
Self {
server: server.clone(),
restore,
@@ -32,7 +37,7 @@ impl Drop for Suppress {
self.server
.log
.reload
.reload(&self.restore)
.reload(&self.restore, Some(&["console"]))
.expect("log filter reloaded");
}
}
+72
View File
@@ -0,0 +1,72 @@
use std::sync::atomic::AtomicU32;
use tokio::runtime;
use tokio_metrics::TaskMonitor;
#[cfg(tokio_unstable)]
use tokio_metrics::{RuntimeIntervals, RuntimeMonitor};
pub struct Metrics {
_runtime: Option<runtime::Handle>,
runtime_metrics: Option<runtime::RuntimeMetrics>,
task_monitor: Option<TaskMonitor>,
#[cfg(tokio_unstable)]
_runtime_monitor: Option<RuntimeMonitor>,
#[cfg(tokio_unstable)]
runtime_intervals: std::sync::Mutex<Option<RuntimeIntervals>>,
// TODO: move stats
pub requests_spawn_active: AtomicU32,
pub requests_spawn_finished: AtomicU32,
pub requests_handle_active: AtomicU32,
pub requests_handle_finished: AtomicU32,
pub requests_panic: AtomicU32,
}
impl Metrics {
#[must_use]
pub fn new(runtime: Option<runtime::Handle>) -> Self {
#[cfg(tokio_unstable)]
let runtime_monitor = runtime.as_ref().map(RuntimeMonitor::new);
#[cfg(tokio_unstable)]
let runtime_intervals = runtime_monitor.as_ref().map(RuntimeMonitor::intervals);
Self {
_runtime: runtime.clone(),
runtime_metrics: runtime.as_ref().map(runtime::Handle::metrics),
task_monitor: runtime.map(|_| TaskMonitor::new()),
#[cfg(tokio_unstable)]
_runtime_monitor: runtime_monitor,
#[cfg(tokio_unstable)]
runtime_intervals: std::sync::Mutex::new(runtime_intervals),
requests_spawn_active: AtomicU32::new(0),
requests_spawn_finished: AtomicU32::new(0),
requests_handle_active: AtomicU32::new(0),
requests_handle_finished: AtomicU32::new(0),
requests_panic: AtomicU32::new(0),
}
}
#[cfg(tokio_unstable)]
pub fn runtime_interval(&self) -> Option<tokio_metrics::RuntimeMetrics> {
self.runtime_intervals
.lock()
.expect("locked")
.as_mut()
.map(Iterator::next)
.expect("next interval")
}
pub fn task_root(&self) -> Option<&TaskMonitor> { self.task_monitor.as_ref() }
pub fn runtime_metrics(&self) -> Option<&runtime::RuntimeMetrics> { self.runtime_metrics.as_ref() }
}
+2 -1
View File
@@ -3,6 +3,7 @@ pub mod config;
pub mod debug;
pub mod error;
pub mod log;
pub mod metrics;
pub mod mods;
pub mod pdu;
pub mod server;
@@ -10,7 +11,7 @@ pub mod utils;
pub mod version;
pub use config::Config;
pub use error::{Error, RumaResponse};
pub use error::Error;
pub use pdu::{PduBuilder, PduCount, PduEvent};
pub use server::Server;
pub use version::version;
+10 -10
View File
@@ -64,7 +64,7 @@ pub struct PduEvent {
}
impl PduEvent {
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn redact(&mut self, room_version_id: RoomVersionId, reason: &Self) -> crate::Result<()> {
self.unsigned = None;
@@ -158,7 +158,7 @@ impl PduEvent {
(self.redacts.clone(), self.content.clone())
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
@@ -183,7 +183,7 @@ impl PduEvent {
}
/// This only works for events that are also AnyRoomEvents.
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_any_event(&self) -> Raw<AnyEphemeralRoomEvent> {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
@@ -208,7 +208,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_room_event(&self) -> Raw<AnyTimelineEvent> {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
@@ -233,7 +233,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
@@ -258,7 +258,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
let mut json = json!({
"content": self.content,
@@ -277,7 +277,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> {
let mut json = json!({
"content": self.content,
@@ -295,7 +295,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
let json = json!({
"content": self.content,
@@ -307,7 +307,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_stripped_spacechild_state_event(&self) -> Raw<HierarchySpaceChildEvent> {
let json = json!({
"content": self.content,
@@ -320,7 +320,7 @@ impl PduEvent {
serde_json::from_value(json).expect("Raw::from_value always works")
}
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(self), level = "debug")]
pub fn to_member_event(&self) -> Raw<StateEvent<RoomMemberEventContent>> {
let mut json = json!({
"content": self.content,
+20 -23
View File
@@ -1,11 +1,11 @@
use std::{
sync::atomic::{AtomicBool, AtomicU32, Ordering},
sync::atomic::{AtomicBool, Ordering},
time::SystemTime,
};
use tokio::{runtime, sync::broadcast};
use crate::{config::Config, log, Error, Result};
use crate::{config::Config, log::Log, metrics::Metrics, Err, Result};
/// Server runtime state; public portion
pub struct Server {
@@ -33,71 +33,68 @@ pub struct Server {
pub signal: broadcast::Sender<&'static str>,
/// Logging subsystem state
pub log: log::Log,
pub log: Log,
/// TODO: move stats
pub requests_spawn_active: AtomicU32,
pub requests_spawn_finished: AtomicU32,
pub requests_handle_active: AtomicU32,
pub requests_handle_finished: AtomicU32,
pub requests_panic: AtomicU32,
/// Metrics subsystem state
pub metrics: Metrics,
}
impl Server {
#[must_use]
pub fn new(config: Config, runtime: Option<runtime::Handle>, log: log::Log) -> Self {
pub fn new(config: Config, runtime: Option<runtime::Handle>, log: Log) -> Self {
Self {
config,
started: SystemTime::now(),
stopping: AtomicBool::new(false),
reloading: AtomicBool::new(false),
restarting: AtomicBool::new(false),
runtime,
runtime: runtime.clone(),
signal: broadcast::channel::<&'static str>(1).0,
log,
requests_spawn_active: AtomicU32::new(0),
requests_spawn_finished: AtomicU32::new(0),
requests_handle_active: AtomicU32::new(0),
requests_handle_finished: AtomicU32::new(0),
requests_panic: AtomicU32::new(0),
metrics: Metrics::new(runtime),
}
}
pub fn reload(&self) -> Result<()> {
if cfg!(not(conduit_mods)) {
return Err(Error::Err("Reloading not enabled".into()));
return Err!("Reloading not enabled");
}
if self.reloading.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Reloading already in progress".into()));
return Err!("Reloading already in progress");
}
if self.stopping.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Shutdown already in progress".into()));
return Err!("Shutdown already in progress");
}
self.signal("SIGINT")
self.signal("SIGINT").inspect_err(|_| {
self.stopping.store(false, Ordering::Release);
self.reloading.store(false, Ordering::Release);
})
}
pub fn restart(&self) -> Result<()> {
if self.restarting.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Restart already in progress".into()));
return Err!("Restart already in progress");
}
self.shutdown()
.inspect_err(|_| self.restarting.store(false, Ordering::Release))
}
pub fn shutdown(&self) -> Result<()> {
if self.stopping.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Shutdown already in progress".into()));
return Err!("Shutdown already in progress");
}
self.signal("SIGTERM")
.inspect_err(|_| self.stopping.store(false, Ordering::Release))
}
pub fn signal(&self, sig: &'static str) -> Result<()> {
if let Err(e) = self.signal.send(sig) {
return Err(Error::Err(format!("Failed to send signal: {e}")));
return Err!("Failed to send signal: {e}");
}
Ok(())
+1 -1
View File
@@ -66,7 +66,7 @@ pub fn content_disposition_type(content_type: &Option<String>) -> &'static str {
/// sanitises the file name for the Content-Disposition using
/// `sanitize_filename` crate
#[tracing::instrument]
#[tracing::instrument(level = "debug")]
pub fn sanitise_filename(filename: String) -> String {
let options = sanitize_filename::Options {
truncate: false,
+2 -8
View File
@@ -1,17 +1,11 @@
#[macro_export]
macro_rules! defer {
($body:block) => {
struct _Defer_<F>
where
F: FnMut(),
{
struct _Defer_<F: FnMut()> {
closure: F,
}
impl<F> Drop for _Defer_<F>
where
F: FnMut(),
{
impl<F: FnMut()> Drop for _Defer_<F> {
fn drop(&mut self) { (self.closure)(); }
}
+2 -2
View File
@@ -5,7 +5,7 @@ use argon2::{
PasswordVerifier, Version,
};
use crate::{Error, Result};
use crate::{err, Error, Result};
const M_COST: u32 = Params::DEFAULT_M_COST; // memory size in 1 KiB blocks
const T_COST: u32 = Params::DEFAULT_T_COST; // nr of iterations
@@ -44,7 +44,7 @@ pub(super) fn verify_password(password: &str, password_hash: &str) -> Result<()>
.map_err(map_err)
}
fn map_err(e: password_hash::Error) -> Error { Error::Err(e.to_string()) }
fn map_err(e: password_hash::Error) -> Error { err!("{e}") }
#[cfg(test)]
mod tests {
+1 -1
View File
@@ -1,6 +1,6 @@
use ring::{digest, digest::SHA256};
#[tracing::instrument(skip_all)]
#[tracing::instrument(skip_all, level = "debug")]
pub(super) fn hash(keys: &[&[u8]]) -> Vec<u8> {
// We only hash the pdu's event ids, not the whole pdu
let bytes = keys.join(&0xFF);
+2 -1
View File
@@ -6,6 +6,7 @@ pub struct Escape<'a>(pub &'a str);
/// Copied from librustdoc:
/// * <https://github.com/rust-lang/rust/blob/cbaeec14f90b59a91a6b0f17fc046c66fa811892/src/librustdoc/html/escape.rs>
#[allow(clippy::string_slice)]
impl fmt::Display for Escape<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
// Because the internet is always right, turns out there's not that many
@@ -26,7 +27,7 @@ impl fmt::Display for Escape<'_> {
fmt.write_str(s)?;
// NOTE: we only expect single byte characters here - which is fine as long as
// we only match single byte characters
last = i + 1;
last = i.saturating_add(1);
}
if last < s.len() {
+88
View File
@@ -0,0 +1,88 @@
use std::{cmp, time::Duration};
pub use checked_ops::checked_ops;
use crate::{Err, Error, Result};
/// Checked arithmetic expression. Returns a Result<R, Error::Arithmetic>
#[macro_export]
macro_rules! checked {
($($input:tt)*) => {
$crate::utils::math::checked_ops!($($input)*)
.ok_or_else(|| $crate::err!(Arithmetic("operation overflowed or result invalid")))
}
}
/// in release-mode. Use for performance when the expression is obviously safe.
/// The check remains in debug-mode for regression analysis.
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! validated {
($($input:tt)*) => {
//#[allow(clippy::arithmetic_side_effects)] {
//Some($($input)*)
// .ok_or_else(|| $crate::err!(Arithmetic("this error should never been seen")))
//}
//NOTE: remove me when stmt_expr_attributes is stable
$crate::checked!($($input)*)
}
}
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! validated {
($($input:tt)*) => { $crate::checked!($($input)*) }
}
/// Returns false if the exponential backoff has expired based on the inputs
#[inline]
#[must_use]
pub fn continue_exponential_backoff_secs(min: u64, max: u64, elapsed: Duration, tries: u32) -> bool {
let min = Duration::from_secs(min);
let max = Duration::from_secs(max);
continue_exponential_backoff(min, max, elapsed, tries)
}
/// Returns false if the exponential backoff has expired based on the inputs
#[inline]
#[must_use]
pub fn continue_exponential_backoff(min: Duration, max: Duration, elapsed: Duration, tries: u32) -> bool {
let min = min.saturating_mul(tries).saturating_mul(tries);
let min = cmp::min(min, max);
elapsed < min
}
#[inline]
#[allow(clippy::as_conversions)]
pub fn usize_from_f64(val: f64) -> Result<usize, Error> {
if val < 0.0 {
return Err!(Arithmetic("Converting negative float to unsigned integer"));
}
//SAFETY: <https://doc.rust-lang.org/std/primitive.f64.html#method.to_int_unchecked>
Ok(unsafe { val.to_int_unchecked::<usize>() })
}
#[inline]
#[must_use]
pub fn usize_from_ruma(val: ruma::UInt) -> usize {
usize::try_from(val).expect("failed conversion from ruma::UInt to usize")
}
#[inline]
#[must_use]
pub fn ruma_from_u64(val: u64) -> ruma::UInt {
ruma::UInt::try_from(val).expect("failed conversion from u64 to ruma::UInt")
}
#[inline]
#[must_use]
pub fn ruma_from_usize(val: usize) -> ruma::UInt {
ruma::UInt::try_from(val).expect("failed conversion from usize to ruma::UInt")
}
#[inline]
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
pub fn usize_from_u64_truncated(val: u64) -> usize { val as usize }
+2 -14
View File
@@ -5,6 +5,7 @@ pub mod defer;
pub mod hash;
pub mod html;
pub mod json;
pub mod math;
pub mod mutex_map;
pub mod rand;
pub mod string;
@@ -19,27 +20,14 @@ pub use debug::slice_truncated as debug_slice_truncated;
pub use hash::calculate_hash;
pub use html::Escape as HtmlEscape;
pub use json::{deserialize_from_str, to_canonical_object};
pub use mutex_map::MutexMap;
pub use mutex_map::{Guard as MutexMapGuard, MutexMap};
pub use rand::string as random_string;
pub use string::{str_from_bytes, string_from_bytes};
pub use sys::available_parallelism;
pub use time::now_millis as millis_since_unix_epoch;
use crate::Result;
pub fn clamp<T: Ord>(val: T, min: T, max: T) -> T { cmp::min(cmp::max(val, min), max) }
/// Boilerplate for wraps which are typed to never error.
///
/// * <https://doc.rust-lang.org/std/convert/enum.Infallible.html>
#[must_use]
pub fn unwrap_infallible<T>(result: Result<T, std::convert::Infallible>) -> T {
match result {
Ok(val) => val,
Err(err) => match err {},
}
}
#[must_use]
pub fn generate_keypair() -> Vec<u8> {
let mut value = rand::string(8).as_bytes().to_vec();
+38 -15
View File
@@ -1,20 +1,22 @@
use std::{hash::Hash, sync::Arc};
use std::{fmt::Debug, hash::Hash, sync::Arc};
type Value<Val> = tokio::sync::Mutex<Val>;
type ArcMutex<Val> = Arc<Value<Val>>;
type HashMap<Key, Val> = std::collections::HashMap<Key, ArcMutex<Val>>;
type MapMutex<Key, Val> = std::sync::Mutex<HashMap<Key, Val>>;
type Map<Key, Val> = MapMutex<Key, Val>;
use tokio::sync::OwnedMutexGuard as Omg;
/// Map of Mutexes
pub struct MutexMap<Key, Val> {
map: Map<Key, Val>,
}
pub struct Guard<Val> {
_guard: tokio::sync::OwnedMutexGuard<Val>,
pub struct Guard<Key, Val> {
map: Map<Key, Val>,
val: Omg<Val>,
}
type Map<Key, Val> = Arc<MapMutex<Key, Val>>;
type MapMutex<Key, Val> = std::sync::Mutex<HashMap<Key, Val>>;
type HashMap<Key, Val> = std::collections::HashMap<Key, Value<Val>>;
type Value<Val> = Arc<tokio::sync::Mutex<Val>>;
impl<Key, Val> MutexMap<Key, Val>
where
Key: Send + Hash + Eq + Clone,
@@ -23,28 +25,38 @@ where
#[must_use]
pub fn new() -> Self {
Self {
map: Map::<Key, Val>::new(HashMap::<Key, Val>::new()),
map: Map::new(MapMutex::new(HashMap::new())),
}
}
pub async fn lock<K>(&self, k: &K) -> Guard<Val>
#[tracing::instrument(skip(self), level = "debug")]
pub async fn lock<K>(&self, k: &K) -> Guard<Key, Val>
where
K: ?Sized + Send + Sync,
K: ?Sized + Send + Sync + Debug,
Key: for<'a> From<&'a K>,
{
let val = self
.map
.lock()
.expect("map mutex locked")
.expect("locked")
.entry(k.into())
.or_default()
.clone();
let guard = val.lock_owned().await;
Guard::<Val> {
_guard: guard,
Guard::<Key, Val> {
map: Arc::clone(&self.map),
val: val.lock_owned().await,
}
}
#[must_use]
pub fn contains(&self, k: &Key) -> bool { self.map.lock().expect("locked").contains_key(k) }
#[must_use]
pub fn is_empty(&self) -> bool { self.map.lock().expect("locked").is_empty() }
#[must_use]
pub fn len(&self) -> usize { self.map.lock().expect("locked").len() }
}
impl<Key, Val> Default for MutexMap<Key, Val>
@@ -54,3 +66,14 @@ where
{
fn default() -> Self { Self::new() }
}
impl<Key, Val> Drop for Guard<Key, Val> {
fn drop(&mut self) {
if Arc::strong_count(Omg::mutex(&self.val)) <= 2 {
self.map
.lock()
.expect("locked")
.retain(|_, val| !Arc::ptr_eq(val, Omg::mutex(&self.val)) || Arc::strong_count(val) > 2);
}
}
}
+5 -1
View File
@@ -15,7 +15,11 @@ pub fn string(length: usize) -> String {
#[inline]
#[must_use]
pub fn timepoint_secs(range: Range<u64>) -> SystemTime { SystemTime::now() + secs(range) }
pub fn timepoint_secs(range: Range<u64>) -> SystemTime {
SystemTime::now()
.checked_add(secs(range))
.expect("range does not overflow SystemTime")
}
#[must_use]
pub fn secs(range: Range<u64>) -> Duration {
+44
View File
@@ -2,6 +2,50 @@ use crate::Result;
pub const EMPTY: &str = "";
/// Constant expression to bypass format! if the argument is a string literal
/// but not a format string. If the literal is a format string then String is
/// returned otherwise the input (i.e. &'static str) is returned. If multiple
/// arguments are provided the first is assumed to be a format string.
#[macro_export]
macro_rules! format_maybe {
($s:literal) => {
if $crate::is_format!($s) { std::format!($s).into() } else { $s.into() }
};
($($args:expr),*) => {
std::format!($($args),*).into()
};
}
/// Constant expression to decide if a literal is a format string. Note: could
/// use some improvement.
#[macro_export]
macro_rules! is_format {
($s:literal) => {
::const_str::contains!($s, "{") && ::const_str::contains!($s, "}")
};
}
/// Find the common prefix from a collection of strings and return a slice
/// ```
/// use conduit_core::utils::string::common_prefix;
/// let input = ["conduwuit", "conduit", "construct"];
/// common_prefix(&input) == "con";
/// ```
#[must_use]
#[allow(clippy::string_slice)]
pub fn common_prefix<'a>(choice: &'a [&str]) -> &'a str {
choice.first().map_or(EMPTY, move |best| {
choice.iter().skip(1).fold(*best, |best, choice| {
&best[0..choice
.char_indices()
.zip(best.char_indices())
.take_while(|&(a, b)| a == b)
.count()]
})
})
}
#[inline]
#[must_use]
pub fn split_once_infallible<'a>(input: &'a str, delim: &'_ str) -> (&'a str, &'a str) {
+99
View File
@@ -35,3 +35,102 @@ fn increment_wrap() {
let res = u64::from_be_bytes(bytes);
assert_eq!(res, 0);
}
#[test]
fn common_prefix() {
use utils::string;
let input = ["conduwuit", "conduit", "construct"];
let output = string::common_prefix(&input);
assert_eq!(output, "con");
}
#[test]
fn common_prefix_empty() {
use utils::string;
let input = ["abcdefg", "hijklmn", "opqrstu"];
let output = string::common_prefix(&input);
assert_eq!(output, "");
}
#[test]
fn common_prefix_none() {
use utils::string;
let input = [];
let output = string::common_prefix(&input);
assert_eq!(output, "");
}
#[test]
fn checked_add() {
use crate::checked;
let a = 1234;
let res = checked!(a + 1).unwrap();
assert_eq!(res, 1235);
}
#[test]
#[should_panic(expected = "overflow")]
fn checked_add_overflow() {
use crate::checked;
let a = u64::MAX;
let res = checked!(a + 1).expect("overflow");
assert_eq!(res, 0);
}
#[tokio::test]
async fn mutex_map_cleanup() {
use crate::utils::MutexMap;
let map = MutexMap::<String, ()>::new();
let lock = map.lock("foo").await;
assert!(!map.is_empty(), "map must not be empty");
drop(lock);
assert!(map.is_empty(), "map must be empty");
}
#[tokio::test]
async fn mutex_map_contend() {
use std::sync::Arc;
use tokio::sync::Barrier;
use crate::utils::MutexMap;
let map = Arc::new(MutexMap::<String, ()>::new());
let seq = Arc::new([Barrier::new(2), Barrier::new(2)]);
let str = "foo".to_owned();
let seq_ = seq.clone();
let map_ = map.clone();
let str_ = str.clone();
let join_a = tokio::spawn(async move {
let _lock = map_.lock(&str_).await;
assert!(!map_.is_empty(), "A0 must not be empty");
seq_[0].wait().await;
assert!(map_.contains(&str_), "A1 must contain key");
});
let seq_ = seq.clone();
let map_ = map.clone();
let str_ = str.clone();
let join_b = tokio::spawn(async move {
let _lock = map_.lock(&str_).await;
assert!(!map_.is_empty(), "B0 must not be empty");
seq_[1].wait().await;
assert!(map_.contains(&str_), "B1 must contain key");
});
seq[0].wait().await;
assert!(map.contains(&str), "Must contain key");
seq[1].wait().await;
tokio::try_join!(join_b, join_a).expect("joined");
assert!(map.is_empty(), "Must be empty");
}
+79 -2
View File
@@ -1,8 +1,8 @@
use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[inline]
#[must_use]
#[allow(clippy::as_conversions)]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
pub fn now_millis() -> u64 {
UNIX_EPOCH
.elapsed()
@@ -26,3 +26,80 @@ pub fn format(ts: SystemTime, str: &str) -> String {
let dt: DateTime<Utc> = ts.into();
dt.format(str).to_string()
}
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn pretty(d: Duration) -> String {
use Unit::*;
let fmt = |w, f, u| format!("{w}.{f} {u}");
let gen64 = |w, f, u| fmt(w, (f * 100.0) as u32, u);
let gen128 = |w, f, u| gen64(u64::try_from(w).expect("u128 to u64"), f, u);
match whole_and_frac(d) {
(Days(whole), frac) => gen64(whole, frac, "days"),
(Hours(whole), frac) => gen64(whole, frac, "hours"),
(Mins(whole), frac) => gen64(whole, frac, "minutes"),
(Secs(whole), frac) => gen64(whole, frac, "seconds"),
(Millis(whole), frac) => gen128(whole, frac, "milliseconds"),
(Micros(whole), frac) => gen128(whole, frac, "microseconds"),
(Nanos(whole), frac) => gen128(whole, frac, "nanoseconds"),
}
}
/// Return a pair of (whole part, frac part) from a duration where. The whole
/// part is the largest Unit containing a non-zero value, the frac part is a
/// rational remainder left over.
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
pub fn whole_and_frac(d: Duration) -> (Unit, f64) {
use Unit::*;
let whole = whole_unit(d);
(
whole,
match whole {
Days(_) => (d.as_secs() % 86_400) as f64 / 86_400.0,
Hours(_) => (d.as_secs() % 3_600) as f64 / 3_600.0,
Mins(_) => (d.as_secs() % 60) as f64 / 60.0,
Secs(_) => f64::from(d.subsec_millis()) / 1000.0,
Millis(_) => f64::from(d.subsec_micros()) / 1000.0,
Micros(_) => f64::from(d.subsec_nanos()) / 1000.0,
Nanos(_) => 0.0,
},
)
}
/// Return the largest Unit which represents the duration. The value is
/// rounded-down, but never zero.
#[must_use]
pub fn whole_unit(d: Duration) -> Unit {
use Unit::*;
match d.as_secs() {
86_400.. => Days(d.as_secs() / 86_400),
3_600..=86_399 => Hours(d.as_secs() / 3_600),
60..=3_599 => Mins(d.as_secs() / 60),
_ => match d.as_micros() {
1_000_000.. => Secs(d.as_secs()),
1_000..=999_999 => Millis(d.subsec_millis().into()),
_ => match d.as_nanos() {
1_000.. => Micros(d.subsec_micros().into()),
_ => Nanos(d.subsec_nanos().into()),
},
},
}
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum Unit {
Days(u64),
Hours(u64),
Mins(u64),
Secs(u64),
Millis(u128),
Micros(u128),
Nanos(u128),
}
+7 -1
View File
@@ -27,5 +27,11 @@ fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
fn init_version() -> String {
option_env!("CONDUWUIT_VERSION_EXTRA")
.or(option_env!("CONDUIT_VERSION_EXTRA"))
.map_or(SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
.map_or(SEMANTIC.to_owned(), |extra| {
if extra.is_empty() {
SEMANTIC.to_owned()
} else {
format!("{SEMANTIC} ({extra})")
}
})
}
+1 -1
View File
@@ -36,8 +36,8 @@ zstd_compression = [
[dependencies]
conduit-core.workspace = true
const-str.workspace = true
log.workspace = true
ruma.workspace = true
rust-rocksdb.workspace = true
tokio.workspace = true
tracing.workspace = true
+1 -1
View File
@@ -6,7 +6,7 @@ use crate::{cork::Cork, maps, maps::Maps, Engine, Map};
pub struct Database {
pub db: Arc<Engine>,
pub map: Maps,
map: Maps,
}
impl Database {

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