1use std::collections::HashSet;
20
21use ed25519_dalek::Signer as _;
22use m4a_matrix_core::{dag, CanonicalJsonObject, CanonicalJsonValue, EventId, StateEventType, OwnedEventId, OwnedRoomId, Pdu, PublicKeyMap, RoomVersionRules, Signer, StateMap};
23use rusqlite::{params, Connection, OptionalExtension, Transaction};
24use serde_json::Value;
25
26use crate::error::MatrixError;
27use crate::store::MatrixStoreError;
28
29pub fn rules() -> RoomVersionRules {
30 m4a_matrix_core::ROOM_VERSION.rules().expect("room version 11 rules")
31}
32
33fn rejected(r: dag::Reject) -> MatrixStoreError {
34 MatrixStoreError::F3Rejected(r.to_string())
35}
36
37pub fn is_f3_room(conn: &Connection, room_id: &str) -> bool {
38 conn.query_row("SELECT 1 FROM dag_rooms WHERE room_id = ?1", [room_id], |_| Ok(())).optional().ok().flatten().is_some()
39}
40
41pub fn mark_room(conn: &Connection, room_id: &str) -> rusqlite::Result<()> {
42 conn.execute("INSERT OR IGNORE INTO dag_rooms (room_id, room_version) VALUES (?1, '11')", [room_id])?;
43 Ok(())
44}
45
46pub fn is_f3_wire(pdu: &Value) -> bool {
48 pdu.get("event_id").is_none() && pdu.get("hashes").is_some() && pdu.get("prev_events").is_some()
49}
50
51fn json_obj(v: &Value) -> Result<CanonicalJsonObject, String> {
52 serde_json::from_value(v.clone()).map_err(|e| e.to_string())
53}
54
55pub struct ConnDag<'a> {
57 pub conn: &'a Connection,
58 pub room: &'a str,
59}
60
61impl dag::DagRead for ConnDag<'_> {
62 fn pdu(&self, id: &EventId) -> Option<Pdu> {
63 let s: String = self.conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id.as_str()], |r| r.get(0)).optional().ok()??;
64 let obj: CanonicalJsonObject = serde_json::from_str(&s).ok()?;
65 Pdu::from_wire(id.to_owned(), &obj).ok()
66 }
67 fn state_after(&self, id: &EventId) -> Option<StateMap<OwnedEventId>> {
68 let gid: i64 = self.conn.query_row("SELECT group_id FROM dag_event_state WHERE event_id = ?1", [id.as_str()], |r| r.get(0)).optional().ok()??;
69 let mut stmt = self.conn.prepare("SELECT event_type, state_key, event_id FROM dag_state_group_entries WHERE group_id = ?1").ok()?;
70 let rows = stmt.query_map([gid], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?, r.get::<_, String>(2)?))).ok()?;
71 let mut m = StateMap::new();
72 for row in rows {
73 let (t, k, e) = row.ok()?;
74 m.insert((StateEventType::from(t), k), OwnedEventId::try_from(e).ok()?);
75 }
76 Some(m)
77 }
78 fn extremities(&self) -> Vec<OwnedEventId> {
79 let Ok(mut stmt) = self.conn.prepare("SELECT event_id FROM dag_extremities WHERE room_id = ?1 ORDER BY event_id") else { return vec![] };
80 let Ok(rows) = stmt.query_map([self.room], |r| r.get::<_, String>(0)) else { return vec![] };
81 rows.filter_map(|r| r.ok()).filter_map(|e| OwnedEventId::try_from(e).ok()).collect()
82 }
83}
84
85fn new_group(conn: &Connection, room: &str, state: &StateMap<OwnedEventId>) -> rusqlite::Result<i64> {
86 conn.execute("INSERT INTO dag_state_groups (room_id) VALUES (?1)", [room])?;
87 let gid = conn.last_insert_rowid();
88 for ((t, k), e) in state {
89 conn.execute("INSERT INTO dag_state_group_entries (group_id, event_type, state_key, event_id) VALUES (?1, ?2, ?3, ?4)", params![gid, t.to_string(), k, e.as_str()])?;
90 }
91 Ok(gid)
92}
93
94fn group_of(conn: &Connection, event_id: &str) -> Option<i64> {
95 conn.query_row("SELECT group_id FROM dag_event_state WHERE event_id = ?1", [event_id], |r| r.get(0)).optional().ok().flatten()
96}
97
98fn store_accepted(conn: &Connection, room: &str, acc: &dag::Accepted, outlier: bool) -> rusqlite::Result<()> {
102 let pdu_json = serde_json::to_string(&acc.json).unwrap_or_default();
103 conn.execute(
104 "INSERT INTO dag_events (event_id, room_id, depth, event_type, sender, state_key, origin_server_ts, pdu, outlier, soft_failed)
105 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
106 params![
107 acc.event_id.as_str(),
108 room,
109 acc.pdu.depth as i64,
110 acc.pdu.kind.to_string(),
111 acc.pdu.sender.as_str(),
112 acc.pdu.state_key,
113 u64::from(acc.pdu.origin_server_ts.0) as i64,
114 pdu_json,
115 outlier as i64,
116 acc.soft_failed as i64
117 ],
118 )?;
119 for p in &acc.pdu.prev_events {
120 conn.execute("INSERT OR IGNORE INTO dag_edges (event_id, prev_event_id) VALUES (?1, ?2)", params![acc.event_id.as_str(), p.as_str()])?;
121 }
122 for a in &acc.pdu.auth_events {
123 conn.execute("INSERT OR IGNORE INTO dag_auth (event_id, auth_event_id) VALUES (?1, ?2)", params![acc.event_id.as_str(), a.as_str()])?;
124 }
125 let shared = if acc.pdu.state_key.is_none() && acc.pdu.prev_events.len() == 1 { group_of(conn, acc.pdu.prev_events[0].as_str()) } else { None };
126 let gid = match shared {
127 Some(g) => g,
128 None => new_group(conn, room, &acc.state_after)?,
129 };
130 conn.execute("INSERT OR REPLACE INTO dag_event_state (event_id, group_id) VALUES (?1, ?2)", params![acc.event_id.as_str(), gid])?;
131 if !acc.soft_failed && !outlier {
132 conn.execute("DELETE FROM dag_extremities WHERE room_id = ?1", [room])?;
133 for e in &acc.extremities {
134 conn.execute("INSERT INTO dag_extremities (room_id, event_id) VALUES (?1, ?2)", params![room, e.as_str()])?;
135 }
136 }
137 conn.execute("INSERT OR REPLACE INTO fed_pdus (event_id, room_id, pdu) VALUES (?1, ?2, ?3)", params![acc.event_id.as_str(), room, pdu_json])?;
138 Ok(())
139}
140
141pub struct Prepared {
143 pub event_id: String,
144 pub content: String,
145}
146
147fn local_signer(conn: &Connection, now_ms: i64) -> Result<Signer, MatrixStoreError> {
148 let (key_id, key) = crate::federation::active_signing_key(conn, now_ms).map_err(|e| MatrixStoreError::F3Rejected(e.to_string()))?;
149 Ok(Signer::new(crate::store::matrix_server_name(), key_id, move |m| key.sign(m).to_bytes()))
150}
151
152pub fn own_public_key(conn: &Connection, key_id: &str) -> Option<String> {
154 let secret: Vec<u8> = conn.query_row("SELECT secret FROM fed_signing_keys WHERE key_id = ?1", [key_id], |r| r.get(0)).optional().ok()??;
155 let bytes: [u8; 32] = secret.try_into().ok()?;
156 let pk = ed25519_dalek::SigningKey::from_bytes(&bytes).verifying_key().to_bytes();
157 Some(m4a_matrix_core::key_to_b64(&pk))
158}
159
160pub fn prepare_local(tx: &Transaction, room_id: &str, sender_user_id: i64, kind: &str, state_key: Option<&str>, content: &str, ts: i64) -> Result<Option<Prepared>, MatrixStoreError> {
164 if !is_f3_room(tx, room_id) {
165 return Ok(None);
166 }
167 let sender = crate::store::mxid_of(tx, sender_user_id)?.ok_or_else(|| MatrixStoreError::UnknownMxid(sender_user_id.to_string()))?;
168 let mut c: Value = serde_json::from_str(content)?;
169 if kind == "m.room.create" && c.get("creator").is_none() {
170 c["creator"] = Value::String(sender.clone());
172 }
173 if kind == "m.room.member" && state_key == Some(sender.as_str()) && c.get("membership").and_then(Value::as_str) == Some("join") && c.get("join_authorised_via_users_server").is_none() {
174 authorise_restricted_join(tx, room_id, &sender, &mut c)?;
175 }
176 let signer = local_signer(tx, ts)?;
177 let room: OwnedRoomId = room_id.try_into().map_err(|e: m4a_matrix_core::IdError| MatrixStoreError::F3Rejected(e.to_string()))?;
178 let db = ConnDag { conn: tx, room: room_id };
179 let acc = dag::local_event(&rules(), &db, &signer, &room, ts.max(0) as u64, &sender, kind, state_key, c.clone()).map_err(rejected)?;
180 store_accepted(tx, room_id, &acc, false)?;
181 Ok(Some(Prepared { event_id: acc.event_id.to_string(), content: c.to_string() }))
182}
183
184fn authorise_restricted_join(tx: &Transaction, room_id: &str, sender: &str, content: &mut Value) -> Result<(), MatrixStoreError> {
188 let jr: Option<String> = tx
189 .query_row("SELECT e.content FROM current_state s JOIN events e ON e.event_id = s.event_id WHERE s.room_id = ?1 AND s.event_type = 'm.room.join_rules' AND s.state_key = ''", [room_id], |r| r.get(0))
190 .optional()?;
191 let restricted = jr.and_then(|c| serde_json::from_str::<Value>(&c).ok()).and_then(|v| v.get("join_rule").and_then(Value::as_str).map(|s| s == "restricted")).unwrap_or(false);
192 if !restricted {
193 return Ok(());
194 }
195 let already: Option<String> = tx.query_row("SELECT m.membership FROM room_members m JOIN matrix_users u ON u.user_id = m.user_id WHERE m.room_id = ?1 AND u.mxid = ?2", params![room_id, sender], |r| r.get(0)).optional()?;
196 if matches!(already.as_deref(), Some("invite") | Some("join")) {
197 return Ok(());
198 }
199 let via: Option<String> = tx
200 .query_row(
201 "SELECT u.mxid FROM room_members m JOIN matrix_users u ON u.user_id = m.user_id JOIN rooms r ON r.id = m.room_id
202 WHERE m.room_id = ?1 AND m.membership = 'join' AND u.user_id > 0 ORDER BY (u.user_id = r.creator_user_id) DESC, u.user_id LIMIT 1",
203 [room_id],
204 |r| r.get(0),
205 )
206 .optional()?;
207 if let Some(v) = via {
208 content["join_authorised_via_users_server"] = Value::String(v);
209 }
210 Ok(())
211}
212
213#[derive(Debug, Default)]
215pub struct Received {
216 pub event_id: String,
217 pub duplicate: bool,
218 pub soft_failed: bool,
219 pub forked: bool,
221 pub wake: HashSet<i64>,
222}
223
224fn bad(e: impl std::fmt::Display) -> MatrixError {
225 MatrixError::forbidden(e.to_string())
226}
227
228fn str_of<'a>(v: &'a Value, k: &str) -> Result<&'a str, MatrixError> {
229 v.get(k).and_then(Value::as_str).ok_or_else(|| MatrixError::bad_json(k.to_string()))
230}
231
232fn ensure_users(conn: &Connection, pdu: &Value, now: &str) -> Result<i64, MatrixError> {
233 let sender = str_of(pdu, "sender")?;
234 let uid = if crate::fed_rooms::is_remote_mxid(sender) {
236 crate::fed_rooms::ensure_remote_user(conn, sender, now).map_err(|_| MatrixError::bad_json("sender"))?
237 } else {
238 crate::store::user_id_of(conn, sender)?.ok_or_else(|| MatrixError::not_found("unknown local user"))?
239 };
240 if pdu.get("type").and_then(Value::as_str) == Some("m.room.member") {
241 let sk = str_of(pdu, "state_key")?;
242 if crate::fed_rooms::is_remote_mxid(sk) {
243 crate::fed_rooms::ensure_remote_user(conn, sk, now).map_err(|_| MatrixError::bad_json("state_key"))?;
244 } else if crate::store::user_id_of(conn, sk)?.is_none() {
245 return Err(MatrixError::not_found("unknown local user"));
246 }
247 }
248 Ok(uid)
249}
250
251fn project(tx: &Transaction, room: &str, acc: &dag::Accepted, prev_current: &StateMap<OwnedEventId>, sender_uid: i64, now: &str) -> Result<(), MatrixStoreError> {
253 let content = acc.pdu.content.get().to_string();
254 let ts = u64::from(acc.pdu.origin_server_ts.0) as i64;
255 let id = acc.event_id.as_str();
256 let kind = acc.pdu.kind.to_string();
257 match &acc.pdu.state_key {
258 Some(sk) => {
259 crate::store::apply_state_event_raw_in_tx(tx, id, room, sender_uid, &kind, sk, &content, ts, now)?;
260 let mut slots: HashSet<(StateEventType, String)> = HashSet::new();
262 slots.insert((StateEventType::from(kind.clone()), sk.clone()));
263 for (k, v) in &acc.current_state {
264 if prev_current.get(k) != Some(v) {
265 slots.insert(k.clone());
266 }
267 }
268 for k in slots {
269 match acc.current_state.get(&k) {
270 Some(w) => {
271 let have: Option<String> = tx.query_row("SELECT event_id FROM current_state WHERE room_id = ?1 AND event_type = ?2 AND state_key = ?3", params![room, k.0.to_string(), k.1], |r| r.get(0)).optional()?;
272 if have.as_deref() != Some(w.as_str()) {
273 crate::store::set_current_state_slot(tx, room, &k.0.to_string(), &k.1, w.as_str(), now)?;
274 }
275 }
276 None => {}
277 }
278 }
279 }
280 None => {
281 let row = crate::store::TimelineEventRow { event_id: id, room_id: room, sender_user_id: sender_uid, event_type: &kind, content: &content, origin_server_ts: ts, txn_id: None };
282 crate::store::insert_timeline_event_raw_in_tx(tx, &row)?;
283 }
284 }
285 Ok(())
286}
287
288#[derive(Debug)]
290pub enum RecvErr {
291 Missing(Vec<String>),
293 Other(MatrixError),
294}
295
296impl From<MatrixError> for RecvErr {
297 fn from(e: MatrixError) -> Self {
298 RecvErr::Other(e)
299 }
300}
301
302fn is_known(conn: &Connection, id: &str) -> bool {
303 conn.query_row(
304 "SELECT 1 FROM dag_events e WHERE e.event_id = ?1 AND (e.outlier = 0 OR EXISTS (SELECT 1 FROM dag_event_state s WHERE s.event_id = e.event_id))",
305 [id],
306 |_| Ok(()),
307 )
308 .optional()
309 .ok()
310 .flatten()
311 .is_some()
312}
313
314pub fn receive_pdu(conn: &mut Connection, origin: &str, pdu: &Value, keys: &PublicKeyMap, now: &str) -> Result<Received, MatrixError> {
318 receive_detail(conn, Some(origin), pdu, keys, now).map_err(|e| match e {
319 RecvErr::Missing(ids) => MatrixError::forbidden(format!("unknown prev or auth events: {}", ids.join(","))),
320 RecvErr::Other(e) => e,
321 })
322}
323
324pub fn receive_detail(conn: &mut Connection, origin: Option<&str>, pdu: &Value, keys: &PublicKeyMap, now: &str) -> Result<Received, RecvErr> {
327 let room = str_of(pdu, "room_id")?.to_string();
328 let sender = str_of(pdu, "sender")?.to_string();
329 if let Some(origin) = origin {
330 if crate::fed_rooms::domain_of(&sender) != Some(origin) || !crate::fed_rooms::is_remote_mxid(&sender) {
331 return Err(MatrixError::forbidden("sender is not a user of the sending server").into());
332 }
333 }
334 if !is_f3_room(conn, &room) {
335 return Err(MatrixError::not_found("unknown room").into());
336 }
337 let obj = json_obj(pdu).map_err(MatrixError::bad_json)?;
338 let rules = rules();
339 let id = dag::compute_event_id(&rules, &obj).map_err(bad)?;
340 let tx = conn.transaction().map_err(|_| MatrixError::internal())?;
341 if is_known(&tx, id.as_str()) {
342 return Ok(Received { event_id: id.to_string(), duplicate: true, ..Default::default() });
343 }
344 let wire = Pdu::from_wire(id.clone(), &obj).map_err(MatrixError::bad_json)?;
346 let missing: Vec<String> = wire.prev_events.iter().chain(wire.auth_events.iter()).filter(|e| !is_known(&tx, e.as_str()) && !(wire.auth_events.contains(e) && ConnDag { conn: &tx, room: &room }.pdu_exists(e))).map(|e| e.to_string()).collect();
347 if !missing.is_empty() {
348 return Err(RecvErr::Missing(missing));
349 }
350 let sender_uid = ensure_users(&tx, pdu, now)?;
351 let db = ConnDag { conn: &tx, room: &room };
352 let prev_current = dag::current_state(&rules, &db).map_err(bad)?;
353 let acc = match dag::accept_wire(&rules, &db, obj, keys, None) {
354 Ok(a) => a,
355 Err(dag::Reject::MissingPrev(e)) | Err(dag::Reject::MissingAuth(e)) => return Err(RecvErr::Missing(vec![e])),
356 Err(e) => return Err(bad(e).into()),
357 };
358 store_accepted(&tx, &room, &acc, false).map_err(|_| MatrixError::internal())?;
359 if dag::is_skeleton(&rules, &acc.json) {
360 let _ = tx.execute("UPDATE dag_events SET skeleton = 1 WHERE event_id = ?1", [acc.event_id.as_str()]);
361 }
362 if !acc.soft_failed {
363 project(&tx, &room, &acc, &prev_current, sender_uid, now).map_err(MatrixError::from)?;
364 }
365 let wake = crate::rooms::member_and_invited_ids(&tx, &room).map_err(|_| MatrixError::internal())?;
366 tx.commit().map_err(|_| MatrixError::internal())?;
367 Ok(Received { event_id: id.to_string(), duplicate: false, soft_failed: acc.soft_failed, forked: acc.forked, wake })
368}
369
370impl ConnDag<'_> {
371 fn pdu_exists(&self, id: &EventId) -> bool {
372 dag::DagRead::pdu(self, id).is_some()
373 }
374}
375
376pub fn state_join_rule_is_public(conn: &Connection, room: &str) -> bool {
380 conn.query_row(
381 "SELECT e.content FROM current_state cs JOIN events e ON e.event_id = cs.event_id WHERE cs.room_id = ?1 AND cs.event_type = 'm.room.join_rules' AND cs.state_key = ''",
382 [room],
383 |r| r.get::<_, String>(0),
384 )
385 .ok()
386 .and_then(|c| serde_json::from_str::<Value>(&c).ok())
387 .is_some_and(|v| v.get("join_rule").and_then(Value::as_str) == Some("public"))
388}
389
390pub fn process_historic(conn: &mut Connection, room: &str, events: &[Value], keys: &PublicKeyMap, now: &str) -> Result<usize, MatrixError> {
395 if !is_f3_room(conn, room) {
396 return Err(MatrixError::not_found("unknown room"));
397 }
398 let rules = rules();
399 let mut parsed: Vec<(OwnedEventId, CanonicalJsonObject, Value)> = Vec::new();
400 for v in events {
401 if v.get("room_id").and_then(Value::as_str) != Some(room) {
402 return Err(MatrixError::bad_json("event of another room"));
403 }
404 let obj = json_obj(v).map_err(MatrixError::bad_json)?;
405 dag::verify_wire(&rules, &obj, keys).map_err(bad)?;
406 let id = dag::compute_event_id(&rules, &obj).map_err(bad)?;
407 parsed.push((id, obj, v.clone()));
408 }
409 parsed.sort_by_key(|(id, o, _)| (match o.get("depth") { Some(CanonicalJsonValue::Integer(i)) => i64::from(*i), _ => 0 }, id.clone()));
410 let tx = conn.transaction().map_err(|_| MatrixError::internal())?;
411 let mut done = 0;
412 for (id, obj, v) in parsed {
413 let has_state = tx.query_row("SELECT 1 FROM dag_event_state WHERE event_id = ?1", [id.as_str()], |_| Ok(())).optional().map_err(|_| MatrixError::internal())?.is_some();
414 if has_state {
415 continue;
416 }
417 let uid = ensure_users(&tx, &v, now)?;
418 let db = ConnDag { conn: &tx, room };
419 let h = dag::accept_historic(&rules, &db, id.clone(), obj.clone()).map_err(bad)?;
421 let text = serde_json::to_string(&obj).unwrap_or_default();
422 let skel = dag::is_skeleton(&rules, &obj);
423 tx.execute(
424 "INSERT OR IGNORE INTO dag_events (event_id, room_id, depth, event_type, sender, state_key, origin_server_ts, pdu, outlier, skeleton) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 1, ?9)",
425 params![id.as_str(), room, h.pdu.depth as i64, h.pdu.kind.to_string(), h.pdu.sender.as_str(), h.pdu.state_key, u64::from(h.pdu.origin_server_ts.0) as i64, text, skel as i64],
426 )
427 .map_err(|_| MatrixError::internal())?;
428 for p in &h.pdu.prev_events {
429 let _ = tx.execute("INSERT OR IGNORE INTO dag_edges (event_id, prev_event_id) VALUES (?1, ?2)", params![id.as_str(), p.as_str()]);
430 }
431 for a in &h.pdu.auth_events {
432 let _ = tx.execute("INSERT OR IGNORE INTO dag_auth (event_id, auth_event_id) VALUES (?1, ?2)", params![id.as_str(), a.as_str()]);
433 }
434 let gid = new_group(&tx, room, &h.state_after).map_err(|_| MatrixError::internal())?;
435 tx.execute("INSERT OR REPLACE INTO dag_event_state (event_id, group_id) VALUES (?1, ?2)", params![id.as_str(), gid]).map_err(|_| MatrixError::internal())?;
436 let _ = tx.execute("INSERT OR REPLACE INTO fed_pdus (event_id, room_id, pdu) VALUES (?1, ?2, ?3)", params![id.as_str(), room, text]);
437 if skel {
438 let _ = tx.execute("INSERT OR IGNORE INTO fed_skeleton (event_id) VALUES (?1)", [id.as_str()]);
439 }
440 let in_events = tx.query_row("SELECT 1 FROM events WHERE event_id = ?1", [id.as_str()], |_| Ok(())).optional().map_err(|_| MatrixError::internal())?.is_some();
441 if !in_events {
442 let content = h.pdu.content.get().to_string();
443 let ts = u64::from(h.pdu.origin_server_ts.0) as i64;
444 let kind = h.pdu.kind.to_string();
445 match &h.pdu.state_key {
446 Some(sk) => crate::store::insert_past_state_row(&tx, id.as_str(), room, uid, &kind, sk, &content, ts).map_err(MatrixError::from)?,
447 None => {
448 let row = crate::store::TimelineEventRow { event_id: id.as_str(), room_id: room, sender_user_id: uid, event_type: &kind, content: &content, origin_server_ts: ts, txn_id: None };
449 crate::store::insert_timeline_event_raw_in_tx(&tx, &row).map_err(MatrixError::from)?;
450 }
451 }
452 }
453 done += 1;
454 }
455 tx.commit().map_err(|_| MatrixError::internal())?;
456 Ok(done)
457}
458
459fn pdu_value(conn: &Connection, id: &str) -> Option<Value> {
460 conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id], |r| r.get::<_, String>(0)).ok().and_then(|s| serde_json::from_str(&s).ok())
461}
462
463fn serve_min_depth(conn: &Connection, room: &str, origin: &str) -> i64 {
466 let shared = crate::store::get_room(conn, room).ok().flatten().is_some_and(|r| matches!(r.history_visibility, crate::store::HistoryVisibility::Shared | crate::store::HistoryVisibility::WorldReadable));
467 if shared {
468 return 0;
469 }
470 conn.query_row("SELECT MIN(depth) FROM dag_events WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key LIKE ?2", params![room, format!("@%:{origin}")], |r| r.get::<_, Option<i64>>(0)).ok().flatten().unwrap_or(i64::MAX)
471}
472
473fn walk_back(conn: &Connection, roots: Vec<String>, stop: &HashSet<String>, limit: usize, min_depth: i64, room: &str) -> Vec<(i64, String)> {
476 let mut seen: HashSet<String> = HashSet::new();
477 let mut queue: std::collections::VecDeque<String> = roots.into();
478 let mut out = Vec::new();
479 while let Some(id) = queue.pop_front() {
480 if out.len() >= limit {
481 break;
482 }
483 if stop.contains(&id) || !seen.insert(id.clone()) {
484 continue;
485 }
486 let Some(depth): Option<i64> = conn.query_row("SELECT depth FROM dag_events WHERE event_id = ?1 AND room_id = ?2", params![id, room], |r| r.get(0)).optional().ok().flatten() else { continue };
487 if depth < min_depth {
488 continue;
489 }
490 out.push((depth, id.clone()));
491 if let Ok(mut st) = conn.prepare("SELECT prev_event_id FROM dag_edges WHERE event_id = ?1") {
492 if let Ok(rows) = st.query_map([&id], |r| r.get::<_, String>(0)) {
493 queue.extend(rows.flatten());
494 }
495 }
496 }
497 out
498}
499
500pub fn origin_in_room(conn: &Connection, room: &str, origin: &str) -> bool {
502 conn.query_row("SELECT 1 FROM dag_events WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key LIKE ?2 LIMIT 1", params![room, format!("@%:{origin}")], |_| Ok(())).optional().ok().flatten().is_some()
503}
504
505pub fn missing_events_json(conn: &Connection, room: &str, origin: &str, earliest: &[String], latest: &[String], limit: usize) -> Vec<Value> {
508 let mut roots = Vec::new();
509 for l in latest {
510 if let Ok(mut st) = conn.prepare("SELECT prev_event_id FROM dag_edges WHERE event_id = ?1") {
511 if let Ok(rows) = st.query_map([l], |r| r.get::<_, String>(0)) {
512 roots.extend(rows.flatten());
513 }
514 }
515 }
516 let stop: HashSet<String> = earliest.iter().cloned().collect();
517 let mut found = walk_back(conn, roots, &stop, limit.clamp(1, 100), serve_min_depth(conn, room, origin), room);
518 found.sort();
519 found.into_iter().filter_map(|(_, id)| pdu_value(conn, &id)).collect()
520}
521
522pub fn backfill_json(conn: &Connection, room: &str, origin: &str, v: &[String], limit: usize) -> Vec<Value> {
524 let mut found = walk_back(conn, v.to_vec(), &HashSet::new(), limit.clamp(1, 100), serve_min_depth(conn, room, origin), room);
525 found.sort_by(|a, b| b.cmp(a));
526 found.into_iter().filter_map(|(_, id)| pdu_value(conn, &id)).collect()
527}
528
529pub fn extremity_ids(conn: &Connection, room: &str) -> Vec<String> {
531 dag::DagRead::extremities(&ConnDag { conn, room }).into_iter().map(|e| e.to_string()).collect()
532}
533
534pub fn snapshot_events(snap: &Value) -> Vec<Value> {
536 ["state", "extremities", "auth_chain"].iter().flat_map(|k| snap.get(*k).and_then(Value::as_array).cloned().unwrap_or_default()).collect()
537}
538
539pub fn snapshot_json(conn: &Connection, room: &str, at: &str) -> Result<Value, MatrixError> {
544 let db = ConnDag { conn, room };
545 let id = OwnedEventId::try_from(at).map_err(|_| MatrixError::bad_json("event id"))?;
546 let after = dag::DagRead::state_after(&db, &id).ok_or_else(|| MatrixError::not_found("unknown event"))?;
547 let get = |id: &str| -> Option<Value> { conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id], |r| r.get::<_, String>(0)).ok().and_then(|s| serde_json::from_str(&s).ok()) };
548 let state: Vec<Value> = after.values().filter_map(|i| get(i.as_str())).collect();
549 let extremities: Vec<Value> = get(at).into_iter().collect();
550 let mut have: HashSet<String> = after.values().map(|i| i.to_string()).collect();
552 have.insert(at.to_string());
553 let mut todo: Vec<String> = have.iter().cloned().collect();
554 let mut chain = Vec::new();
555 while let Some(id) = todo.pop() {
556 if let Ok(mut st) = conn.prepare("SELECT auth_event_id FROM dag_auth WHERE event_id = ?1") {
557 let ids: Vec<String> = st.query_map([&id], |r| r.get::<_, String>(0)).map(|r| r.flatten().collect()).unwrap_or_default();
558 for a in ids {
559 if have.insert(a.clone()) {
560 if let Some(p) = get(&a) {
561 chain.push(p);
562 }
563 todo.push(a);
564 }
565 }
566 }
567 }
568 Ok(serde_json::json!({ "state": state, "extremities": extremities, "auth_chain": chain }))
569}
570
571pub fn auth_chain_of(conn: &Connection, seeds: &[String]) -> Vec<Value> {
573 let get = |id: &str| -> Option<Value> { conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id], |r| r.get::<_, String>(0)).ok().and_then(|s| serde_json::from_str(&s).ok()) };
574 let mut have: HashSet<String> = seeds.iter().cloned().collect();
575 let mut todo: Vec<String> = seeds.to_vec();
576 let mut chain = Vec::new();
577 while let Some(id) = todo.pop() {
578 let ids: Vec<String> = conn
579 .prepare("SELECT auth_event_id FROM dag_auth WHERE event_id = ?1")
580 .and_then(|mut st| st.query_map([&id], |r| r.get::<_, String>(0)).map(|r| r.flatten().collect()))
581 .unwrap_or_default();
582 for a in ids {
583 if have.insert(a.clone()) {
584 if let Some(p) = get(&a) {
585 chain.push(p);
586 }
587 todo.push(a);
588 }
589 }
590 }
591 chain
592}
593
594pub fn event_auth_json(conn: &Connection, room: &str, event_id: &str) -> Result<Value, MatrixError> {
596 let known: Option<i64> = conn.query_row("SELECT 1 FROM dag_events WHERE event_id = ?1", [event_id], |r| r.get(0)).optional().ok().flatten();
597 if known.is_none() {
598 return Err(MatrixError::not_found("unknown event"));
599 }
600 let _ = room;
601 Ok(serde_json::json!({ "auth_chain": auth_chain_of(conn, &[event_id.to_string()]) }))
602}
603
604pub fn state_at_json(conn: &Connection, room: &str, at: &str, ids_only: bool) -> Result<Value, MatrixError> {
606 let db = ConnDag { conn, room };
607 let id = OwnedEventId::try_from(at).map_err(|_| MatrixError::bad_json("event id"))?;
608 let after = dag::DagRead::state_after(&db, &id).ok_or_else(|| MatrixError::not_found("unknown event"))?;
609 let ids: Vec<String> = after.values().map(|i| i.to_string()).collect();
610 let chain = auth_chain_of(conn, &ids);
611 if ids_only {
612 let chain_ids: Vec<String> = chain.iter().filter_map(|p| pdu_event_id(p)).collect();
613 return Ok(serde_json::json!({ "pdu_ids": ids, "auth_chain_ids": chain_ids }));
614 }
615 let get = |id: &str| -> Option<Value> { conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id], |r| r.get::<_, String>(0)).ok().and_then(|s| serde_json::from_str(&s).ok()) };
616 let pdus: Vec<Value> = ids.iter().filter_map(|i| get(i)).collect();
617 Ok(serde_json::json!({ "pdus": pdus, "auth_chain": chain }))
618}
619
620fn pdu_event_id(p: &Value) -> Option<String> {
621 let obj = json_obj(p).ok()?;
622 dag::compute_event_id(&rules(), &obj).ok().map(|i| i.to_string())
623}
624
625pub fn import_snapshot(conn: &mut Connection, room_id: &str, info: &Value, snap: &Value, keys: &PublicKeyMap, now: &str) -> Result<(), MatrixError> {
629 let rules = rules();
630 let list = |k: &str| snap.get(k).and_then(Value::as_array).cloned().unwrap_or_default();
631 let (state_v, ext_v) = (list("state"), list("extremities"));
632 let tx = conn.transaction().map_err(|_| MatrixError::internal())?;
633 crate::fed_rooms::create_replica_room(&tx, room_id, info, now)?;
634 mark_room(&tx, room_id).map_err(|_| MatrixError::internal())?;
635 let ext_ids: HashSet<OwnedEventId> = ext_v.iter().filter_map(|e| json_obj(e).ok()).filter_map(|o| dag::compute_event_id(&rules, &o).ok()).collect();
636 let chain_v = list("auth_chain");
639 let all_objs: Vec<CanonicalJsonObject> = state_v.iter().chain(ext_v.iter()).chain(chain_v.iter()).map(|v| json_obj(v).map_err(MatrixError::bad_json)).collect::<Result<_, _>>()?;
640 dag::verify_auth_chain(&rules, &all_objs, keys).map_err(bad)?;
641 let mut parsed: Vec<(OwnedEventId, CanonicalJsonObject, Value)> = Vec::new();
642 for v in state_v.iter().chain(ext_v.iter()).chain(chain_v.iter()) {
643 if v.get("room_id").and_then(Value::as_str) != Some(room_id) {
644 return Err(MatrixError::bad_json("event of another room"));
645 }
646 let obj = json_obj(v).map_err(MatrixError::bad_json)?;
647 dag::verify_wire(&rules, &obj, keys).map_err(bad)?;
648 let id = dag::compute_event_id(&rules, &obj).map_err(bad)?;
649 if parsed.iter().all(|(i, _, _)| *i != id) {
650 parsed.push((id, obj, v.clone()));
651 }
652 }
653 parsed.sort_by_key(|(_, o, _)| match o.get("depth") {
654 Some(CanonicalJsonValue::Integer(i)) => i64::from(*i),
655 _ => 0,
656 });
657 let mut state: StateMap<OwnedEventId> = StateMap::new();
658 let state_ids: HashSet<OwnedEventId> = state_v.iter().filter_map(|e| json_obj(e).ok()).filter_map(|o| dag::compute_event_id(&rules, &o).ok()).collect();
659 for (id, obj, v) in &parsed {
660 let pdu = Pdu::from_wire(id.clone(), obj).map_err(MatrixError::bad_json)?;
661 let uid = ensure_users(&tx, v, now)?;
662 let known = tx.query_row("SELECT 1 FROM dag_events WHERE event_id = ?1", [id.as_str()], |_| Ok(())).optional().map_err(|_| MatrixError::internal())?.is_some();
663 if !known {
664 tx.execute(
666 "INSERT INTO dag_events (event_id, room_id, depth, event_type, sender, state_key, origin_server_ts, pdu, outlier) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 1)",
667 params![id.as_str(), room_id, pdu.depth as i64, pdu.kind.to_string(), pdu.sender.as_str(), pdu.state_key, u64::from(pdu.origin_server_ts.0) as i64, serde_json::to_string(obj).unwrap_or_default()],
668 )
669 .map_err(|_| MatrixError::internal())?;
670 for p in &pdu.prev_events {
671 let _ = tx.execute("INSERT OR IGNORE INTO dag_edges (event_id, prev_event_id) VALUES (?1, ?2)", params![id.as_str(), p.as_str()]);
672 }
673 for a in &pdu.auth_events {
674 let _ = tx.execute("INSERT OR IGNORE INTO dag_auth (event_id, auth_event_id) VALUES (?1, ?2)", params![id.as_str(), a.as_str()]);
675 }
676 let _ = tx.execute("INSERT OR REPLACE INTO fed_pdus (event_id, room_id, pdu) VALUES (?1, ?2, ?3)", params![id.as_str(), room_id, serde_json::to_string(obj).unwrap_or_default()]);
677 }
678 if let Some(sk) = &pdu.state_key {
679 if state_ids.contains(id) {
680 state.insert((StateEventType::from(pdu.kind.to_string()), sk.clone()), id.clone());
681 if !known {
682 let content = pdu.content.get().to_string();
683 crate::store::apply_state_event_raw_in_tx(&tx, id.as_str(), room_id, uid, &pdu.kind.to_string(), sk, &content, u64::from(pdu.origin_server_ts.0) as i64, now).map_err(MatrixError::from)?;
684 }
685 }
686 }
687 }
688 for ((t, k), id) in &state {
690 let have: Option<String> = tx.query_row("SELECT event_id FROM current_state WHERE room_id = ?1 AND event_type = ?2 AND state_key = ?3", params![room_id, t.to_string(), k], |r| r.get(0)).optional().map_err(|_| MatrixError::internal())?;
691 if have.as_deref() != Some(id.as_str()) {
692 crate::store::set_current_state_slot(&tx, room_id, &t.to_string(), k, id.as_str(), now).map_err(MatrixError::from)?;
693 }
694 }
695 let _ = tx.execute("DELETE FROM current_state WHERE room_id = ?1 AND event_id LIKE '$stripped-%'", [room_id]);
697 let _ = tx.execute("DELETE FROM events WHERE room_id = ?1 AND event_id LIKE '$stripped-%'", [room_id]);
698 let gid = new_group(&tx, room_id, &state).map_err(|_| MatrixError::internal())?;
699 tx.execute("DELETE FROM dag_extremities WHERE room_id = ?1", [room_id]).map_err(|_| MatrixError::internal())?;
700 for (id, _, _) in &parsed {
701 if ext_ids.contains(id) {
702 tx.execute("INSERT OR REPLACE INTO dag_event_state (event_id, group_id) VALUES (?1, ?2)", params![id.as_str(), gid]).map_err(|_| MatrixError::internal())?;
703 tx.execute("INSERT OR IGNORE INTO dag_extremities (room_id, event_id) VALUES (?1, ?2)", params![room_id, id.as_str()]).map_err(|_| MatrixError::internal())?;
704 }
705 }
706 tx.commit().map_err(|_| MatrixError::internal())?;
707 Ok(())
708}
709
710pub fn import_spec_invite(conn: &mut Connection, room_id: &str, invite: &Value, stripped: &[Value], keys: &PublicKeyMap, now: &str) -> Result<(), MatrixError> {
715 let rules = rules();
716 let obj = json_obj(invite).map_err(MatrixError::bad_json)?;
717 dag::verify_wire(&rules, &obj, keys).map_err(bad)?;
718 let id = dag::compute_event_id(&rules, &obj).map_err(bad)?;
719 let pdu = Pdu::from_wire(id.clone(), &obj).map_err(MatrixError::bad_json)?;
720 if pdu.room_id.as_str() != room_id {
721 return Err(MatrixError::bad_json("event of another room"));
722 }
723 let has = |ty: &str| stripped.iter().any(|s| s.get("type").and_then(Value::as_str) == Some(ty));
724 let info = serde_json::json!({ "kind": "group", "join_rule": "invite", "history_visibility": "shared", "creator": pdu.sender.as_str(), "is_encrypted": has("m.room.encryption") });
725 let tx = conn.transaction().map_err(|_| MatrixError::internal())?;
726 crate::fed_rooms::create_replica_room(&tx, room_id, &info, now)?;
727 mark_room(&tx, room_id).map_err(|_| MatrixError::internal())?;
728 let known = tx.query_row("SELECT 1 FROM dag_events WHERE event_id = ?1", [id.as_str()], |_| Ok(())).optional().map_err(|_| MatrixError::internal())?.is_some();
729 if !known {
730 let uid = ensure_users(&tx, invite, now)?;
731 let ts = u64::from(pdu.origin_server_ts.0) as i64;
732 let text = serde_json::to_string(&obj).unwrap_or_default();
733 tx.execute(
734 "INSERT INTO dag_events (event_id, room_id, depth, event_type, sender, state_key, origin_server_ts, pdu, outlier) VALUES (?1, ?2, ?3, 'm.room.member', ?4, ?5, ?6, ?7, 1)",
735 params![id.as_str(), room_id, pdu.depth as i64, pdu.sender.as_str(), pdu.state_key, ts, text],
736 )
737 .map_err(|_| MatrixError::internal())?;
738 let _ = tx.execute("INSERT OR REPLACE INTO fed_pdus (event_id, room_id, pdu) VALUES (?1, ?2, ?3)", params![id.as_str(), room_id, text]);
739 for (n, s) in stripped.iter().enumerate() {
740 let (Some(ty), Some(sk)) = (s.get("type").and_then(Value::as_str), s.get("state_key").and_then(Value::as_str)) else { continue };
741 if ty == "m.room.member" {
742 continue;
743 }
744 let content = s.get("content").cloned().unwrap_or_else(|| serde_json::json!({}));
745 let ph = format!("$stripped-{n}-{}", id.as_str().trim_start_matches('$'));
746 let _ = crate::store::apply_state_event_raw_in_tx(&tx, &ph, room_id, uid, ty, sk, &content.to_string(), ts, now);
747 }
748 crate::store::apply_state_event_raw_in_tx(&tx, id.as_str(), room_id, uid, "m.room.member", pdu.state_key.as_deref().unwrap_or(""), pdu.content.get(), ts, now).map_err(MatrixError::from)?;
749 let mut state: StateMap<OwnedEventId> = StateMap::new();
750 state.insert((StateEventType::from("m.room.member".to_string()), pdu.state_key.clone().unwrap_or_default()), id.clone());
751 let gid = new_group(&tx, room_id, &state).map_err(|_| MatrixError::internal())?;
752 tx.execute("INSERT OR REPLACE INTO dag_event_state (event_id, group_id) VALUES (?1, ?2)", params![id.as_str(), gid]).map_err(|_| MatrixError::internal())?;
753 tx.execute("INSERT OR IGNORE INTO dag_extremities (room_id, event_id) VALUES (?1, ?2)", params![room_id, id.as_str()]).map_err(|_| MatrixError::internal())?;
754 }
755 tx.commit().map_err(|_| MatrixError::internal())?;
756 Ok(())
757}
758
759pub fn wire_id(pdu: &Value) -> Result<String, MatrixError> {
761 let obj = json_obj(pdu).map_err(MatrixError::bad_json)?;
762 Ok(dag::compute_event_id(&rules(), &obj).map_err(bad)?.to_string())
763}
764
765pub fn missing_keys(conn: &Connection, pdus: &[&Value], keys: &mut PublicKeyMap) -> Vec<(String, String)> {
768 let mut need = Vec::new();
769 for p in pdus {
770 let Some(sigs) = p.get("signatures").and_then(Value::as_object) else { continue };
771 for (server, ks) in sigs {
772 for key_id in ks.as_object().into_iter().flat_map(|o| o.keys()) {
773 if keys.get(server).is_some_and(|m| m.contains_key(key_id)) {
774 continue;
775 }
776 if crate::store::is_local_server_name(server) {
777 if let Some(pk) = own_public_key(conn, key_id).and_then(|b| m4a_matrix_core::key_from_b64(&b).ok()) {
778 keys.entry(server.clone()).or_default().insert(key_id.clone(), pk);
779 continue;
780 }
781 }
782 if !need.contains(&(server.clone(), key_id.clone())) {
783 need.push((server.clone(), key_id.clone()));
784 }
785 }
786 }
787 }
788 need
789}
790
791pub fn add_key(keys: &mut PublicKeyMap, server: &str, key_id: &str, public_key_b64: &str) -> Result<(), MatrixError> {
793 let pk = m4a_matrix_core::key_from_b64(public_key_b64).map_err(|_| MatrixError::bad_json("remote key"))?;
794 keys.entry(server.to_owned()).or_default().insert(key_id.to_owned(), pk);
795 Ok(())
796}
797
798pub fn skeletonize(conn: &Connection, event_id: &str) -> Result<bool, MatrixStoreError> {
802 let pending: i64 = conn.query_row("SELECT COUNT(*) FROM fed_outbox WHERE event_id = ?1", [event_id], |r| r.get(0))?;
803 if pending > 0 {
804 return Ok(false);
805 }
806 let Some(s): Option<String> = conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [event_id], |r| r.get(0)).optional()? else { return Ok(false) };
807 let obj: CanonicalJsonObject = serde_json::from_str(&s)?;
808 let sk = dag::skeleton(&rules(), &obj).map_err(rejected)?;
809 let text = serde_json::to_string(&sk)?;
810 conn.execute("UPDATE dag_events SET pdu = ?2, skeleton = 1 WHERE event_id = ?1", params![event_id, text])?;
811 conn.execute("UPDATE fed_pdus SET pdu = ?2 WHERE event_id = ?1", params![event_id, text])?;
812 conn.execute("UPDATE events SET content = '{}' WHERE event_id = ?1", [event_id])?;
813 conn.execute("INSERT OR IGNORE INTO fed_skeleton (event_id) VALUES (?1)", [event_id])?;
814 Ok(true)
815}
816
817pub fn join_template(conn: &Connection, room_id: &str, user_mxid: &str, ts: i64) -> Result<Value, MatrixError> {
820 let room: OwnedRoomId = room_id.try_into().map_err(|_| MatrixError::bad_json("room id"))?;
821 let db = ConnDag { conn, room: room_id };
822 let t = dag::template(&rules(), &db, &room, ts.max(0) as u64, user_mxid, "m.room.member", Some(user_mxid), serde_json::json!({ "membership": "join" })).map_err(bad)?;
823 serde_json::to_value(t).map_err(|_| MatrixError::internal())
824}
825
826pub fn leave_template(conn: &Connection, room_id: &str, user_mxid: &str, ts: i64) -> Result<Value, MatrixError> {
828 let room: OwnedRoomId = room_id.try_into().map_err(|_| MatrixError::bad_json("room id"))?;
829 let db = ConnDag { conn, room: room_id };
830 let t = dag::template(&rules(), &db, &room, ts.max(0) as u64, user_mxid, "m.room.member", Some(user_mxid), serde_json::json!({ "membership": "leave" })).map_err(bad)?;
831 serde_json::to_value(t).map_err(|_| MatrixError::internal())
832}
833
834pub fn pdu_json(conn: &Connection, event_id: &str) -> Option<Value> {
836 conn.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [event_id], |r| r.get::<_, String>(0)).ok().and_then(|s| serde_json::from_str(&s).ok())
837}
838
839pub fn room_of_event(conn: &Connection, event_id: &str) -> Option<String> {
841 conn.query_row("SELECT room_id FROM dag_events WHERE event_id = ?1", [event_id], |r| r.get::<_, String>(0)).ok()
842}
843
844pub fn sign_own(conn: &Connection, template: &Value, now_ms: i64) -> Result<(String, Value), MatrixError> {
846 let signer = local_signer(conn, now_ms)?;
847 let obj = json_obj(template).map_err(MatrixError::bad_json)?;
848 let s = dag::sign_template(&rules(), &signer, obj).map_err(bad)?;
849 Ok((s.event_id.to_string(), serde_json::to_value(&s.json).map_err(|_| MatrixError::internal())?))
850}
851
852
853#[cfg(test)]
854mod tests {
855 use super::*;
856 use crate::rooms::{apply_create_room, apply_invite, decide_and_apply_join, InviteTarget, RoomCreate, RoomCreation};
857 use crate::store;
858 use m4a_matrix_core::{key_to_b64, Signer};
859
860 const NOW: &str = "2026-10-10T00:00:00+00:00";
861 const TS: i64 = 1_760_000_000_000;
862
863 fn conn() -> Connection {
864 let c = Connection::open_in_memory().unwrap();
865 store::create_matrix_schema(&c).unwrap();
866 store::ensure_matrix_user(&c, 1, "alice000000000000000000000000001", NOW).unwrap();
867 store::ensure_matrix_user(&c, 2, "bob00000000000000000000000000002", NOW).unwrap();
868 c
869 }
870
871 fn create(c: &mut Connection, public: bool) -> String {
872 let mxid = store::mxid_of(c, 1).unwrap().unwrap();
873 match apply_create_room(c, RoomCreate { creator_user_id: 1, creator_mxid: &mxid, creator_displayname: "alice", is_direct: false, invitees: &[], visibility_public: public, power_level_content_override: None, name: Some("t"), topic: None, room_type: None, predecessor: None }, NOW, TS).unwrap() {
874 RoomCreation::Created { room_id, .. } => room_id,
875 RoomCreation::Reused(_) => unreachable!(),
876 }
877 }
878
879 fn ids(c: &Connection, room: &str) -> Vec<String> {
880 c.prepare("SELECT event_id FROM events WHERE room_id = ?1 ORDER BY stream_id").unwrap().query_map([room], |r| r.get(0)).unwrap().map(Result::unwrap).collect()
881 }
882
883 fn count(c: &Connection, sql: &str) -> i64 {
884 c.query_row(sql, [], |r| r.get(0)).unwrap()
885 }
886
887 fn is_hash_id(id: &str) -> bool {
888 id.starts_with('$') && id.len() == 44 && !id.contains(['+', '/', '='])
889 }
890
891 fn send(c: &mut Connection, room: &str, txn: &str, ts: i64) -> store::MatrixEvent {
892 let r = store::get_room(c, room).unwrap().unwrap();
893 let mxid = store::mxid_of(c, 1).unwrap().unwrap();
894 crate::messaging::apply_send(c, &r, 1, &mxid, "DEV", txn, &store::new_event_id(), "m.room.encrypted", r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"c2VjcmV0","session_id":"s","sender_key":"k","device_id":"DEV"}"#, NOW, ts).unwrap().event
895 }
896
897 #[test]
898 fn closed_room_lifecycle_events_all_get_hash_ids_and_dag_rows() {
899 let mut c = conn();
900 let room = create(&mut c, false);
901 let alice = store::mxid_of(&c, 1).unwrap().unwrap();
902 let bob = store::mxid_of(&c, 2).unwrap().unwrap();
903 apply_invite(&mut c, &room, 1, &alice, InviteTarget { user_id: 2, displayname: "bob" }, NOW, TS + 10).unwrap();
904 decide_and_apply_join(&mut c, &room, 2, &bob, "bob", NOW, TS + 20).unwrap();
905 let m = send(&mut c, &room, "t1", TS + 30);
906 crate::rooms::apply_leave(&mut c, &room, 2, &bob, NOW, TS + 40).unwrap();
907
908 let all = ids(&c, &room);
909 assert!(all.len() >= 9, "bootstrap + invite + join + message + leave: {all:?}");
910 for id in &all {
911 assert!(is_hash_id(id), "{id}");
912 assert!(count(&c, &format!("SELECT COUNT(*) FROM dag_events WHERE event_id = '{id}' AND soft_failed = 0 AND outlier = 0")) == 1);
913 let pdu: String = c.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [id], |r| r.get(0)).unwrap();
914 let v: Value = serde_json::from_str(&pdu).unwrap();
915 assert!(v["hashes"]["sha256"].is_string() && v["signatures"][store::matrix_server_name()].is_object() && v.get("event_id").is_none());
916 assert!(v["depth"].as_u64().unwrap() >= 1);
917 }
918 assert!(is_hash_id(&m.event_id), "the id the sender gets back is the computed one");
919 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_events"), all.len() as i64);
920 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_extremities"), 1);
921 let last: String = c.query_row("SELECT event_id FROM dag_extremities", [], |r| r.get(0)).unwrap();
922 assert_eq!(&last, all.last().unwrap(), "linear history: the last event is the only extremity");
923 let db = ConnDag { conn: &c, room: &room };
925 let cur = dag::current_state(&rules(), &db).unwrap();
926 let table: i64 = count(&c, &format!("SELECT COUNT(*) FROM current_state WHERE room_id = '{room}'"));
927 assert_eq!(cur.len() as i64, table);
928 for ((t, k), id) in &cur {
929 let have: String = c.query_row("SELECT event_id FROM current_state WHERE room_id = ?1 AND event_type = ?2 AND state_key = ?3", params![room, t.to_string(), k], |r| r.get(0)).unwrap();
930 assert_eq!(have, id.as_str());
931 }
932 let bob_state = store::room_member(&c, &room, 2).unwrap().unwrap().membership;
933 assert_eq!(bob_state, store::Membership::Leave);
934 let again = send(&mut c, &room, "t1", TS + 31);
936 assert_eq!(again.event_id, m.event_id);
937 }
938
939 #[test]
940 fn the_event_write_and_the_dag_rows_roll_back_together() {
941 let mut c = conn();
942 let room = create(&mut c, false);
943 let before = (count(&c, "SELECT COUNT(*) FROM dag_events"), count(&c, "SELECT COUNT(*) FROM events"));
944 {
945 let tx = c.transaction().unwrap();
946 let p = prepare_local(&tx, &room, 1, "m.room.message", None, r#"{"body":"x"}"#, TS + 5).unwrap().unwrap();
947 assert!(is_hash_id(&p.event_id));
948 assert_eq!(count(&tx, "SELECT COUNT(*) FROM dag_events"), before.0 + 1);
949 tx.rollback().unwrap();
950 }
951 assert_eq!((count(&c, "SELECT COUNT(*) FROM dag_events"), count(&c, "SELECT COUNT(*) FROM events")), before);
952 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_extremities"), 1);
953 }
954
955 #[test]
956 fn auth_rules_stop_an_event_in_the_same_transaction() {
957 let mut c = conn();
958 let room = create(&mut c, false);
959 let before = count(&c, "SELECT COUNT(*) FROM dag_events");
960 let tx = c.transaction().unwrap();
962 let r = prepare_local(&tx, &room, 2, "m.room.name", Some(""), r#"{"name":"x"}"#, TS + 6);
963 assert!(matches!(r, Err(MatrixStoreError::F3Rejected(_))));
964 drop(tx);
965 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_events"), before);
966 }
967
968 #[test]
969 fn legacy_rooms_and_public_channels_are_untouched() {
970 let mut c = conn();
971 let chan = create(&mut c, true);
972 assert!(!is_f3_room(&c, &chan));
973 for id in ids(&c, &chan) {
974 assert!(!is_hash_id(&id) && id.len() < 30, "legacy random id: {id}");
975 }
976 c.execute("INSERT INTO rooms (id, kind, creator_user_id, created_at) VALUES ('!old:example.org', 'group', 1, 't')", []).unwrap();
978 store::apply_state_event(&mut c, &store::StateEventWrite { event_id: "$chosen", room_id: "!old:example.org", sender_user_id: 1, event_type: "m.room.topic", state_key: "", content: r#"{"topic":"t"}"#, origin_server_ts: TS, now: NOW }).unwrap();
979 assert_eq!(ids(&c, "!old:example.org"), vec!["$chosen".to_string()]);
980 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_events WHERE room_id = '!old:example.org'"), 0);
981 }
982
983 #[test]
984 fn skeleton_erases_content_keeps_id_and_signatures() {
985 let mut c = conn();
986 let room = create(&mut c, false);
987 let m = send(&mut c, &room, "t1", TS + 30);
988 c.execute("INSERT INTO fed_outbox (destination, kind, room_id, event_id, payload, created_ms) VALUES ('b.example', 'send', ?1, ?2, '{}', 1)", params![room, m.event_id]).unwrap();
989 assert!(!skeletonize(&c, &m.event_id).unwrap(), "refused while a delivery is queued");
990 c.execute("DELETE FROM fed_outbox", []).unwrap();
991 assert!(skeletonize(&c, &m.event_id).unwrap());
992 let content: String = c.query_row("SELECT content FROM events WHERE event_id = ?1", [&m.event_id], |r| r.get(0)).unwrap();
993 assert_eq!(content, "{}");
994 let pdu: String = c.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [&m.event_id], |r| r.get(0)).unwrap();
995 let obj: CanonicalJsonObject = serde_json::from_str(&pdu).unwrap();
996 assert_eq!(dag::compute_event_id(&rules(), &obj).unwrap().as_str(), m.event_id, "the skeleton still hashes to the same id");
997 assert!(!pdu.contains("c2VjcmV0") && pdu.contains("signatures") && pdu.contains("prev_events"));
998 }
999
1000 fn recv(c: &mut Connection, origin: &str, pdu: &Value) -> Result<Received, MatrixError> {
1001 let keys = peer_keys(c, pdu);
1002 receive_pdu(c, origin, pdu, &keys, NOW)
1003 }
1004
1005 #[test]
1006 fn retention_turns_delivered_messages_of_dag_rooms_into_skeletons_instead_of_deleting_them() {
1007 let mut c = conn();
1008 crate::keys::create_matrix_keys_schema(&c).unwrap();
1009 crate::retention::create_retention_schema(&c).unwrap();
1010 let room = create(&mut c, false);
1011 let m = send(&mut c, &room, "t1", TS + 30);
1012 let policy = crate::retention::RetentionPolicy { ttl_ms: 1_000, ack_grace_ms: 0, keep_last: 0, stale_device_ms: 1 };
1013 let n = crate::retention::purge_delivered_events(&mut c, TS + 10_000_000, &policy).unwrap();
1014 assert_eq!(n, 1);
1015 let content: String = c.query_row("SELECT content FROM events WHERE event_id = ?1", [&m.event_id], |r| r.get(0)).unwrap();
1017 assert_eq!(content, "{}");
1018 let skel: i64 = c.query_row("SELECT skeleton FROM dag_events WHERE event_id = ?1", [&m.event_id], |r| r.get(0)).unwrap();
1019 assert_eq!(skel, 1);
1020 assert_eq!(crate::retention::purge_delivered_events(&mut c, TS + 10_000_000, &policy).unwrap(), 0);
1022 }
1023
1024 fn replica_conn() -> Connection {
1027 conn()
1028 }
1029
1030 fn a_keys(a: &Connection, events: &[Value]) -> PublicKeyMap {
1031 let mut keys = PublicKeyMap::new();
1032 let refs: Vec<&Value> = events.iter().collect();
1033 assert!(missing_keys(a, &refs, &mut keys).is_empty());
1034 keys
1035 }
1036
1037 fn room_with_bob_joined(c: &mut Connection) -> String {
1038 let room = create(c, false);
1039 let alice = store::mxid_of(c, 1).unwrap().unwrap();
1040 let bob = store::mxid_of(c, 2).unwrap().unwrap();
1041 apply_invite(c, &room, 1, &alice, InviteTarget { user_id: 2, displayname: "bob" }, NOW, TS + 10).unwrap();
1042 decide_and_apply_join(c, &room, 2, &bob, "bob", NOW, TS + 20).unwrap();
1043 room
1044 }
1045
1046 #[test]
1047 fn a_joiner_reverifies_the_auth_chain_and_refuses_a_missing_or_altered_link() {
1048 let mut a = conn();
1049 let room = room_with_bob_joined(&mut a);
1050 let last = extremity_ids(&a, &room).remove(0);
1051 let snap = snapshot_json(&a, &room, &last).unwrap();
1052 let chain = snap["auth_chain"].as_array().unwrap().clone();
1053 assert!(!chain.is_empty(), "bob's invite is superseded by his join: it travels in the chain");
1054 let info = crate::fed_rooms::room_info(&a, &room).unwrap();
1055
1056 let try_import = |snap: &Value| {
1057 let mut b = replica_conn();
1058 let keys = a_keys(&a, &snapshot_events(snap));
1059 let r = import_snapshot(&mut b, &room, &info, snap, &keys, NOW);
1060 (r, b)
1061 };
1062 let (ok, b) = try_import(&snap);
1063 ok.unwrap();
1064 assert_eq!(count(&b, "SELECT COUNT(*) FROM dag_extremities"), 1);
1065 assert_eq!(store::room_member(&b, &room, 2).unwrap().unwrap().membership, store::Membership::Join);
1066
1067 let mut cut = snap.clone();
1068 cut["auth_chain"].as_array_mut().unwrap().remove(0);
1069 let (r, b) = try_import(&cut);
1070 assert!(r.is_err(), "a chain with a link missing is refused");
1071 assert_eq!(count(&b, "SELECT COUNT(*) FROM rooms"), 0, "and nothing is left behind");
1072
1073 let mut altered = snap.clone();
1074 altered["auth_chain"][0]["origin_server_ts"] = Value::from(5);
1075 assert!(try_import(&altered).0.is_err(), "an altered link is refused");
1076 }
1077
1078 #[test]
1079 fn history_is_served_oldest_first_without_the_asked_for_events_and_skeletons_arrive_as_skeletons() {
1080 let mut a = conn();
1081 let room = room_with_bob_joined(&mut a);
1082 let before = extremity_ids(&a, &room).remove(0);
1083 let m1 = send(&mut a, &room, "t1", TS + 30);
1084 let m2 = send(&mut a, &room, "t2", TS + 31);
1085 let ids = |v: &[Value]| -> Vec<String> { v.iter().map(|p| wire_id(p).unwrap()).collect() };
1086
1087 let got = missing_events_json(&a, &room, "example.org", &[before.clone()], &[m2.event_id.clone()], 10);
1089 assert_eq!(ids(&got), vec![m1.event_id.clone()]);
1090 let bf = backfill_json(&a, &room, "example.org", &[m2.event_id.clone()], 100);
1092 let order = ids(&bf);
1093 assert_eq!(order[..2], [m2.event_id.clone(), m1.event_id.clone()]);
1094
1095 assert!(skeletonize(&a, &m1.event_id).unwrap());
1097 let served = missing_events_json(&a, &room, "example.org", &[before.clone()], &[m2.event_id.clone()], 10);
1098 assert_eq!(served[0]["content"], serde_json::json!({}));
1099 assert_eq!(wire_id(&served[0]).unwrap(), m1.event_id, "same id, same signatures");
1100
1101 let mut b = replica_conn();
1103 let snap = snapshot_json(&a, &room, &before).unwrap();
1104 let info = crate::fed_rooms::room_info(&a, &room).unwrap();
1105 let keys = a_keys(&a, &snapshot_events(&snap));
1106 import_snapshot(&mut b, &room, &info, &snap, &keys, NOW).unwrap();
1107 let keys = a_keys(&a, &served);
1108 assert_eq!(process_historic(&mut b, &room, &served, &keys, NOW).unwrap(), 1);
1109 let content: String = b.query_row("SELECT content FROM events WHERE event_id = ?1", [&m1.event_id], |r| r.get(0)).unwrap();
1110 assert_eq!(content, "{}");
1111 assert_eq!(count(&b, &format!("SELECT skeleton FROM dag_events WHERE event_id = '{}'", m1.event_id)), 1);
1112 assert_eq!(process_historic(&mut b, &room, &served, &keys, NOW).unwrap(), 0);
1114 let mut forged = served.clone();
1115 forged[0]["content"] = serde_json::json!({"body": "x"});
1116 assert!(process_historic(&mut b, &room, &forged, &keys, NOW).is_err());
1117 }
1118
1119 fn peer_signer() -> Signer {
1120 let key = ed25519_dalek::SigningKey::from_bytes(&[5u8; 32]);
1121 Signer::new("b.example", "ed25519:b1", move |m| key.sign(m).to_bytes())
1122 }
1123
1124 fn peer_keys(c: &Connection, pdu: &Value) -> PublicKeyMap {
1125 let mut keys = PublicKeyMap::new();
1126 let pk = ed25519_dalek::SigningKey::from_bytes(&[5u8; 32]).verifying_key().to_bytes();
1127 add_key(&mut keys, "b.example", "ed25519:b1", &key_to_b64(&pk)).unwrap();
1128 assert!(missing_keys(c, &[pdu], &mut keys).is_empty(), "our own keys resolve locally, the peer's was supplied");
1129 keys
1130 }
1131
1132 #[test]
1133 fn a_peers_join_is_checked_projected_and_a_stale_prev_forks_then_merges() {
1134 let mut c = conn();
1135 let room = create(&mut c, false);
1136 let alice = store::mxid_of(&c, 1).unwrap().unwrap();
1137 let bob = "@bob:b.example";
1138 let bob_uid = crate::fed_rooms::ensure_remote_user(&c, bob, NOW).unwrap();
1139 apply_invite(&mut c, &room, 1, &alice, InviteTarget { user_id: bob_uid, displayname: "bob" }, NOW, TS + 10).unwrap();
1140
1141 let mut tpl = join_template(&c, &room, bob, TS + 20).unwrap();
1143 tpl["content"]["displayname"] = Value::String("bob".into());
1144 let m = send(&mut c, &room, "t1", TS + 25);
1146 let signed = dag::sign_template(&rules(), &peer_signer(), json_obj(&tpl).unwrap()).unwrap();
1147 let wire = serde_json::to_value(&signed.json).unwrap();
1148
1149 let rows = count(&c, "SELECT COUNT(*) FROM dag_events");
1151 let mut forged = wire.clone();
1152 forged["origin_server_ts"] = Value::from(1);
1153 assert!(recv(&mut c, "b.example", &forged).is_err());
1154 let stranger = ed25519_dalek::SigningKey::from_bytes(&[9u8; 32]);
1155 let other = Signer::new("b.example", "ed25519:b1", move |m| stranger.sign(m).to_bytes());
1156 let wrong = serde_json::to_value(&dag::sign_template(&rules(), &other, json_obj(&tpl).unwrap()).unwrap().json).unwrap();
1157 assert!(recv(&mut c, "b.example", &wrong).is_err());
1158 assert!(recv(&mut c, "c.example", &wire).is_err());
1160 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_events"), rows);
1161
1162 let got = recv(&mut c, "b.example", &wire).unwrap();
1163 assert_eq!(got.event_id, signed.event_id.as_str());
1164 assert!(got.forked && !got.soft_failed, "bob's join cites the invite, alice's message is a second branch");
1165 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_extremities"), 2);
1166 assert_eq!(store::room_member(&c, &room, bob_uid).unwrap().unwrap().membership, store::Membership::Join, "projected into the room's membership");
1167 assert!(recv(&mut c, "b.example", &wire).unwrap().duplicate);
1168
1169 let m2 = send(&mut c, &room, "t2", TS + 50);
1171 assert_eq!(count(&c, "SELECT COUNT(*) FROM dag_extremities"), 1);
1172 let pdu: String = c.query_row("SELECT pdu FROM dag_events WHERE event_id = ?1", [&m2.event_id], |r| r.get(0)).unwrap();
1173 let v: Value = serde_json::from_str(&pdu).unwrap();
1174 assert_eq!(v["prev_events"].as_array().unwrap().len(), 2);
1175 assert!(v["prev_events"].as_array().unwrap().iter().any(|p| p == m.event_id.as_str()));
1176
1177 let snap = snapshot_json(&c, &room, &m2.event_id).unwrap();
1179 assert!(snap["state"].as_array().unwrap().len() >= 7 && snap["extremities"].as_array().unwrap().len() == 1);
1180 }
1181}