atagen

macltok

public
Star0
Watch0

Commit dc609022bb53

tarball

atagen <boss@atagen.co>2026-10-22 09:31 UTC

7 files changed+65-72
engine: share the room key while the user types, on a forked sdk

The public-only warm-up could only sync members, leaving /keys/claim and the room key share on the send path, which is the slow federated half. A matrix-rust-sdk fork exposing Room::prepare_encryption runs the send queue's own prep early, inside its per-room share dedup, so a send after a warm-up is just the PUT. The patch points at a sibling checkout until the fork has a git URL.
Modified file: Cargo.lock−20 deletions
@@ -1999,8 +1999,6 @@ dependencies = [
19991999 [[package]]
20002000 name = "matrix-sdk"
20012001 version = "0.18.0"
2002-source = "registry+https://github.com/rust-lang/crates.io-index"
2003-checksum = "7083d580527511ac5d9369e03b9f2b20902e76949f1b3964051f978e4d3756ae"
20042002 dependencies = [
20052003 "anymap2",
20062004 "aquamarine",
@@ -2063,8 +2061,6 @@ dependencies = [
20632061 [[package]]
20642062 name = "matrix-sdk-base"
20652063 version = "0.18.0"
2066-source = "registry+https://github.com/rust-lang/crates.io-index"
2067-checksum = "e09a917eb1f7643d9d9a06b2f131e2ceb39df6910a7b830dac18bfd6db37a1f5"
20682064 dependencies = [
20692065 "as_variant",
20702066 "assert_matches",
@@ -2094,8 +2090,6 @@ dependencies = [
20942090 [[package]]
20952091 name = "matrix-sdk-common"
20962092 version = "0.18.0"
2097-source = "registry+https://github.com/rust-lang/crates.io-index"
2098-checksum = "2b9d1e0fee0f090180ef9457034adc547e80de9e6e3eb2e63aaac68a8476f836"
20992093 dependencies = [
21002094 "eyeball-im",
21012095 "futures-core",
@@ -2118,8 +2112,6 @@ dependencies = [
21182112 [[package]]
21192113 name = "matrix-sdk-crypto"
21202114 version = "0.18.0"
2121-source = "registry+https://github.com/rust-lang/crates.io-index"
2122-checksum = "c54afd2a326f51c13a6ad44ec86315a688fffeb3f1e287fb343e0e1836a3bdaf"
21232115 dependencies = [
21242116 "aes",
21252117 "aquamarine",
@@ -2160,8 +2152,6 @@ dependencies = [
21602152 [[package]]
21612153 name = "matrix-sdk-indexeddb"
21622154 version = "0.18.0"
2163-source = "registry+https://github.com/rust-lang/crates.io-index"
2164-checksum = "fef37395fffb7c916f7109ab0d16d8ca599403dd8164d08c0d966176b66ede47"
21652155 dependencies = [
21662156 "async-trait",
21672157 "base64",
@@ -2193,8 +2183,6 @@ dependencies = [
21932183 [[package]]
21942184 name = "matrix-sdk-sqlite"
21952185 version = "0.18.0"
2196-source = "registry+https://github.com/rust-lang/crates.io-index"
2197-checksum = "a49133429271005745f8d5a05362d2ba6567274777250236799927ca30dc0670"
21982186 dependencies = [
21992187 "as_variant",
22002188 "async-trait",
@@ -2221,8 +2209,6 @@ dependencies = [
22212209 [[package]]
22222210 name = "matrix-sdk-store-encryption"
22232211 version = "0.18.0"
2224-source = "registry+https://github.com/rust-lang/crates.io-index"
2225-checksum = "2f48f304e553fb6200b1d7d1f77a88fd182076d4b25624e9dbfa42d6a37de35e"
22262212 dependencies = [
22272213 "base64",
22282214 "blake3",
@@ -2243,8 +2229,6 @@ dependencies = [
22432229 [[package]]
22442230 name = "matrix-sdk-test"
22452231 version = "0.18.0"
2246-source = "registry+https://github.com/rust-lang/crates.io-index"
2247-checksum = "8023d4315fff35598c2d4a955920c1273c6aaab4dcd9ed405238a882581649bf"
22482232 dependencies = [
22492233 "as_variant",
22502234 "ctor",
@@ -2267,8 +2251,6 @@ dependencies = [
22672251 [[package]]
22682252 name = "matrix-sdk-test-macros"
22692253 version = "0.18.0"
2270-source = "registry+https://github.com/rust-lang/crates.io-index"
2271-checksum = "d97d6a0e5b1401c530b3c5d18cb98257d255d396bacf3903beda6718a2f4c4c5"
22722254 dependencies = [
22732255 "quote",
22742256 "syn 2.0.119",
@@ -2277,8 +2259,6 @@ dependencies = [
22772259 [[package]]
22782260 name = "matrix-sdk-ui"
22792261 version = "0.18.0"
2280-source = "registry+https://github.com/rust-lang/crates.io-index"
2281-checksum = "6d1f2b8a8cf57164d7cee4eb61c0b9f074b2e11d766a9391b7db0b9f2cbd83d8"
22822262 dependencies = [
22832263 "as_variant",
22842264 "async-rx",
Modified file: Cargo.toml+12 additions
@@ -76,3 +76,15 @@ unsafe_code = "forbid"
7676
7777 [lints.clippy]
7878 all = "warn"
79+
80+[patch.crates-io]
81+matrix-sdk = { path = "../matrix-rust-sdk/crates/matrix-sdk" }
82+matrix-sdk-base = { path = "../matrix-rust-sdk/crates/matrix-sdk-base" }
83+matrix-sdk-common = { path = "../matrix-rust-sdk/crates/matrix-sdk-common" }
84+matrix-sdk-crypto = { path = "../matrix-rust-sdk/crates/matrix-sdk-crypto" }
85+matrix-sdk-indexeddb = { path = "../matrix-rust-sdk/crates/matrix-sdk-indexeddb" }
86+matrix-sdk-sqlite = { path = "../matrix-rust-sdk/crates/matrix-sdk-sqlite" }
87+matrix-sdk-store-encryption = { path = "../matrix-rust-sdk/crates/matrix-sdk-store-encryption" }
88+matrix-sdk-test = { path = "../matrix-rust-sdk/testing/matrix-sdk-test" }
89+matrix-sdk-test-macros = { path = "../matrix-rust-sdk/testing/matrix-sdk-test-macros" }
90+matrix-sdk-ui = { path = "../matrix-rust-sdk/crates/matrix-sdk-ui" }
Modified file: docs/design-decisions.md+31 additions−27 deletions
@@ -124,30 +124,18 @@ cancels it
124124 the sdk does all its key work on the send path, in the room's send queue before
125125 the `/send` itself. members synced, a `/keys/query` for anyone untracked or
126126 dirty, then a `/keys/claim` and the room key sent to every device. over a slow
127-federation that's a minute of nothing. `prepare_to_send` does what the public
128-api lets us of that, ahead of time, which comes down to syncing the members if
129-they aren't. the sdk dedups that with its own sync, and it marks every member
130-tracked and dirty, so the sdk's background outgoing requests run the
131-`/keys/query` long before the send would have. that's what moves the query off
132-the send path. we don't ask `request_user_identity` for anyone, since that's
133-an out of band query that never marks a user tracked or up to date
134-(matrix-sdk-crypto `identities/manager.rs` ~804-817), federated traffic the
135-send path would redo anyway. a synced room costs nothing, so there's no
136-cooldown. one warm-up per room at a time, a second ask joins it. opening a room
137-or starting to type runs one too, unless `EngineConfig::prepare_to_send` is
138-off. it's on because malice asked, not because anyone decided
139-
140-that's the leftovers of turning down a matrix-sdk fork. the `/keys/claim` and
141-the room key share, the slow federated half, stay on the send path. the
142-background `/keys/query` runs when the sdk next sends its outgoing requests,
143-after a sync response, and `send_outgoing_requests` is crate private, so a send
144-straight after a warm-up can still find the members dirty and query them itself.
145-and we can't join the dedup the sdk wraps its own prep in
146-
147-the hook upstream is the todo above `preshare_room_key` at matrix-sdk
148-`room/mod.rs` ~2392, asking for exactly this. it's still private on main and in
149-0.19.1 as of 2026-09-26. once upstream makes it public, or a fork becomes
150-acceptable, `prepare_to_send` should just call it and most of this goes
127+federation that's a minute of nothing. `prepare_to_send` runs all of it ahead
128+of time through the fork's `Room::prepare_encryption`, the same function the
129+send queue calls. the share sits behind the sdk's per-room dedup, so a send
130+landing mid-warm-up waits for it rather than sharing again, and a send after it
131+finds nothing left to do but the `/send`. a room whose encryption isn't known
132+yet gets asked, which the send would have done anyway. one warm-up per room at
133+a time, a second ask joins it. opening a room or starting to type runs one too,
134+unless `EngineConfig::prepare_to_send` is off. it's on because malice asked,
135+not because anyone decided
136+
137+the session rotates after 100 messages or a week, and sharing isn't a message,
138+so a warm-up is only wasted on a room left idle about that long
151139
152140 ## mutations
153141
@@ -302,9 +290,7 @@ keyed by `CsrfToken` (matrix-sdk 0.18, `authentication/oauth/mod.rs` around
302290 `finish_login` on a fresh client answers `InvalidState`. doing the code grant
303291 ourselves would copy some 250 lines of the sdk's private oauth behaviour and
304292 drift from it every upgrade. patching the sdk to export and import that data is
305-small, but it means carrying a matrix-sdk fork, which we won't for now. upstream
306-exposing export and import of pending authorization data, or a fork becoming
307-acceptable, reopens this
293+small, and since we carry a fork now that's the way to do it. not built yet
308294
309295 ## room lists, spaces and profiles
310296
@@ -359,6 +345,24 @@ most. a diff for a timeline with no reset is a divergence too, so a wrapper
359345 that swallowed the reset still heals. anything a late subscriber needs, failures
360346 included, lives in the state rather than only in an update
361347
348+## the sdk fork
349+
350+we build against a fork of matrix-rust-sdk, branch `macltok`, cut from the
351+matrix-sdk 0.18.0 tag. turned down on 2026-09-26, taken up on 2026-10-22 once
352+it was holding back features every peer client has. every workspace that builds
353+macltok needs the same `[patch.crates-io]` block, since patches don't reach
354+dependents, and it keeps macltok off crates.io until upstream takes the patches.
355+a vendored copy of macltok should drop its own block, cargo ignores it there
356+and says so on every build
357+
358+it carries visibility and config changes, each small enough to be an upstream
359+pr, and nothing that changes how the sdk behaves. if that stops being true,
360+especially in the timeline or the send queue, it's time to drop matrix-sdk and
361+matrix-sdk-ui for our own layer over matrix-sdk-base and matrix-sdk-crypto,
362+which keep their public sans-io apis
363+
364+- `Room::prepare_encryption`, the send path's encryption prep made public
365+
362366 ## public model
363367
364368 owned ruma ids and typed data wherever there's a stable shape, `Raw` where
Modified file: harness/web/Cargo.lock−16 deletions
@@ -1905,8 +1905,6 @@ dependencies = [
19051905 [[package]]
19061906 name = "matrix-sdk"
19071907 version = "0.18.0"
1908-source = "registry+https://github.com/rust-lang/crates.io-index"
1909-checksum = "7083d580527511ac5d9369e03b9f2b20902e76949f1b3964051f978e4d3756ae"
19101908 dependencies = [
19111909 "anymap2",
19121910 "aquamarine",
@@ -1966,8 +1964,6 @@ dependencies = [
19661964 [[package]]
19671965 name = "matrix-sdk-base"
19681966 version = "0.18.0"
1969-source = "registry+https://github.com/rust-lang/crates.io-index"
1970-checksum = "e09a917eb1f7643d9d9a06b2f131e2ceb39df6910a7b830dac18bfd6db37a1f5"
19711967 dependencies = [
19721968 "as_variant",
19731969 "async-trait",
@@ -1993,8 +1989,6 @@ dependencies = [
19931989 [[package]]
19941990 name = "matrix-sdk-common"
19951991 version = "0.18.0"
1996-source = "registry+https://github.com/rust-lang/crates.io-index"
1997-checksum = "2b9d1e0fee0f090180ef9457034adc547e80de9e6e3eb2e63aaac68a8476f836"
19981992 dependencies = [
19991993 "eyeball-im",
20001994 "futures-core",
@@ -2017,8 +2011,6 @@ dependencies = [
20172011 [[package]]
20182012 name = "matrix-sdk-crypto"
20192013 version = "0.18.0"
2020-source = "registry+https://github.com/rust-lang/crates.io-index"
2021-checksum = "c54afd2a326f51c13a6ad44ec86315a688fffeb3f1e287fb343e0e1836a3bdaf"
20222014 dependencies = [
20232015 "aes",
20242016 "aquamarine",
@@ -2058,8 +2050,6 @@ dependencies = [
20582050 [[package]]
20592051 name = "matrix-sdk-indexeddb"
20602052 version = "0.18.0"
2061-source = "registry+https://github.com/rust-lang/crates.io-index"
2062-checksum = "fef37395fffb7c916f7109ab0d16d8ca599403dd8164d08c0d966176b66ede47"
20632053 dependencies = [
20642054 "async-trait",
20652055 "base64",
@@ -2091,8 +2081,6 @@ dependencies = [
20912081 [[package]]
20922082 name = "matrix-sdk-sqlite"
20932083 version = "0.18.0"
2094-source = "registry+https://github.com/rust-lang/crates.io-index"
2095-checksum = "a49133429271005745f8d5a05362d2ba6567274777250236799927ca30dc0670"
20962084 dependencies = [
20972085 "as_variant",
20982086 "async-trait",
@@ -2119,8 +2107,6 @@ dependencies = [
21192107 [[package]]
21202108 name = "matrix-sdk-store-encryption"
21212109 version = "0.18.0"
2122-source = "registry+https://github.com/rust-lang/crates.io-index"
2123-checksum = "2f48f304e553fb6200b1d7d1f77a88fd182076d4b25624e9dbfa42d6a37de35e"
21242110 dependencies = [
21252111 "base64",
21262112 "blake3",
@@ -2141,8 +2127,6 @@ dependencies = [
21412127 [[package]]
21422128 name = "matrix-sdk-ui"
21432129 version = "0.18.0"
2144-source = "registry+https://github.com/rust-lang/crates.io-index"
2145-checksum = "6d1f2b8a8cf57164d7cee4eb61c0b9f074b2e11d766a9391b7db0b9f2cbd83d8"
21462130 dependencies = [
21472131 "as_variant",
21482132 "async-rx",
Modified file: harness/web/Cargo.toml+10 additions
@@ -45,3 +45,13 @@ lto = true
4545 codegen-units = 1
4646 panic = "abort"
4747 strip = true
48+
49+[patch.crates-io]
50+matrix-sdk = { path = "../../../matrix-rust-sdk/crates/matrix-sdk" }
51+matrix-sdk-base = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-base" }
52+matrix-sdk-common = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-common" }
53+matrix-sdk-crypto = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-crypto" }
54+matrix-sdk-indexeddb = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-indexeddb" }
55+matrix-sdk-sqlite = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-sqlite" }
56+matrix-sdk-store-encryption = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-store-encryption" }
57+matrix-sdk-ui = { path = "../../../matrix-rust-sdk/crates/matrix-sdk-ui" }
Modified file: src/engine/prepare.rs+5 additions−9 deletions
@@ -33,15 +33,11 @@ impl Preparations {
3333 }
3434 }
3535
36-/// the part of the sdk's pre-send encryption work it lets us reach. syncing the members
37-/// has the sdk track them all, and its own background `/keys/query` then catches up
38-/// their devices. the one time key claim and the room key share stay on the send path
36+/// the sdk's own pre-send work, run early. a send landing mid-share waits on the
37+/// sdk's per-room share lock instead of starting another (forked sdk, see design-decisions)
3938 async fn prepare(client: Client, room_id: OwnedRoomId) -> Result<(), Error> {
4039 let room = client.get_room(&room_id).ok_or_else(Error::room_unknown)?;
41- if room.encryption_state().is_encrypted() && !room.are_members_synced() {
42- room.sync_members()
43- .await
44- .map_err(|source| Error::from_sdk("syncing room members failed", source))?;
45- }
46- Ok(())
40+ room.prepare_encryption()
41+ .await
42+ .map_err(|source| Error::from_sdk("preparing room encryption failed", source))
4743 }
Modified file: tests/prepare.rs+7 additions
@@ -45,6 +45,13 @@ async fn prepare_twice(encrypted: bool, member_syncs: u64) {
4545 .expect(member_syncs)
4646 .mount()
4747 .await;
48+ let encryption = server.mock_room_state_encryption();
49+ if encrypted {
50+ encryption.encrypted().mount().await;
51+ } else {
52+ encryption.plain().mount().await;
53+ }
54+ server.mock_query_keys().ok().mount().await;
4855 let config = EngineConfig::default()
4956 .with_initial_history_limit(0)
5057 .with_prepare_to_send(false);