1use rusqlite::{params, Connection, OptionalExtension, Transaction};
47use std::collections::HashSet;
48use std::fmt;
49
50static SERVER_NAME_CELL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
58
59pub fn set_matrix_server_name(name: impl Into<String>) -> Result<(), &'static str> {
62 let name = name.into();
63 let (host, port) = name.split_once(':').map_or((name.as_str(), None), |(h, p)| (h, Some(p)));
66 if host.is_empty() || host.contains('/') || host.contains(':') || port.is_some_and(|p| p.parse::<u16>().is_err()) {
67 return Err("server name must be a hostname, optionally with :port");
68 }
69 SERVER_NAME_CELL.set(name).map_err(|_| "server name already set")
70}
71
72static LOCAL_ALIASES: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
73
74
75pub fn set_local_aliases(names: impl IntoIterator<Item = String>) {
83 let list: Vec<String> = names.into_iter().map(|n| n.trim().to_ascii_lowercase()).filter(|n| !n.is_empty() && !n.contains(':') && !n.contains('/')).collect();
84 let _ = LOCAL_ALIASES.set(list);
85}
86
87pub fn is_local_server_name(name: &str) -> bool {
89 name == matrix_server_name() || LOCAL_ALIASES.get().is_some_and(|aliases| aliases.iter().any(|a| a.eq_ignore_ascii_case(name)))
90}
91
92pub fn matrix_server_name() -> &'static str {
94 SERVER_NAME_CELL.get_or_init(|| "example.org".to_string()).as_str()
95}
96
97pub const MATRIX_ROOM_VERSION: &str = "11";
102
103pub const MATRIX_EVENT_CONTENT_MAX_BYTES: usize = 16 * 1024;
109
110pub const RESERVED_LOCALPART_PREFIX: &str = "_bridge_";
115
116pub fn is_reserved_localpart(localpart: &str) -> bool {
120 localpart.starts_with(RESERVED_LOCALPART_PREFIX)
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum MatrixIdError {
126 MissingSigil,
128 MissingServerName,
130 ForeignServerName,
134}
135
136impl fmt::Display for MatrixIdError {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 MatrixIdError::MissingSigil => write!(f, "mxid is missing its '@' sigil"),
140 MatrixIdError::MissingServerName => write!(f, "mxid is missing a ':server_name' suffix"),
141 MatrixIdError::ForeignServerName => write!(f, "mxid is addressed to a foreign server name"),
142 }
143 }
144}
145
146pub fn mxid_for_public_id(public_id: &str) -> String {
149 format!("@{public_id}:{}", matrix_server_name())
150}
151
152pub fn public_id_from_mxid(mxid: &str) -> Result<&str, MatrixIdError> {
156 let rest = mxid.strip_prefix('@').ok_or(MatrixIdError::MissingSigil)?;
157 let (localpart, server_name) = rest.split_once(':').ok_or(MatrixIdError::MissingServerName)?;
158 if !is_local_server_name(server_name) {
159 return Err(MatrixIdError::ForeignServerName);
160 }
161 Ok(localpart)
162}
163
164fn random_id_component() -> String {
167 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
168 use base64::Engine;
169 use rand::Rng;
170 let bytes: [u8; 16] = rand::thread_rng().gen();
171 URL_SAFE_NO_PAD.encode(bytes)
172}
173
174pub fn new_room_id() -> String {
176 format!("!{}:{}", random_id_component(), matrix_server_name())
177}
178
179pub fn new_event_id() -> String {
182 format!("${}", random_id_component())
183}
184
185#[derive(Debug)]
192pub enum MatrixStoreError {
193 Db(rusqlite::Error),
194 Json(serde_json::Error),
197 ReservedLocalpart,
200 DuplicateAnnotation,
204 UnknownEventId(String),
206 UnknownMxid(String),
209 InvalidMembership(String),
212 InvalidRelationTarget(String),
217 WrongRoom(String),
222 UnredactableEvent(String),
226 F3Rejected(String),
229}
230
231impl From<rusqlite::Error> for MatrixStoreError {
232 fn from(e: rusqlite::Error) -> Self {
233 MatrixStoreError::Db(e)
234 }
235}
236
237impl From<serde_json::Error> for MatrixStoreError {
238 fn from(e: serde_json::Error) -> Self {
239 MatrixStoreError::Json(e)
240 }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum RoomKind {
252 Dm,
253 Group,
254 Channel,
255}
256
257impl RoomKind {
258 pub fn as_str(self) -> &'static str {
259 match self {
260 RoomKind::Dm => "dm",
261 RoomKind::Group => "group",
262 RoomKind::Channel => "channel",
263 }
264 }
265
266 pub fn from_wire_name(s: &str) -> Option<Self> {
267 match s {
268 "dm" => Some(RoomKind::Dm),
269 "group" => Some(RoomKind::Group),
270 "channel" => Some(RoomKind::Channel),
271 _ => None,
272 }
273 }
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub enum JoinRule {
278 Invite,
279 Public,
280}
281
282impl JoinRule {
283 pub fn as_str(self) -> &'static str {
284 match self {
285 JoinRule::Invite => "invite",
286 JoinRule::Public => "public",
287 }
288 }
289
290 pub fn from_wire_name(s: &str) -> Option<Self> {
291 match s {
292 "invite" => Some(JoinRule::Invite),
293 "public" => Some(JoinRule::Public),
294 _ => None,
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum HistoryVisibility {
301 Shared,
302 WorldReadable,
303 Invited,
304 Joined,
305}
306
307impl HistoryVisibility {
308 pub fn as_str(self) -> &'static str {
309 match self {
310 HistoryVisibility::Shared => "shared",
311 HistoryVisibility::WorldReadable => "world_readable",
312 HistoryVisibility::Invited => "invited",
313 HistoryVisibility::Joined => "joined",
314 }
315 }
316
317 pub fn from_wire_name(s: &str) -> Option<Self> {
318 match s {
319 "shared" => Some(HistoryVisibility::Shared),
320 "world_readable" => Some(HistoryVisibility::WorldReadable),
321 "invited" => Some(HistoryVisibility::Invited),
322 "joined" => Some(HistoryVisibility::Joined),
323 _ => None,
324 }
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum Membership {
330 Join,
331 Invite,
332 Leave,
333 Ban,
334}
335
336impl Membership {
337 pub fn as_str(self) -> &'static str {
338 match self {
339 Membership::Join => "join",
340 Membership::Invite => "invite",
341 Membership::Leave => "leave",
342 Membership::Ban => "ban",
343 }
344 }
345
346 pub fn from_wire_name(s: &str) -> Option<Self> {
347 match s {
348 "join" => Some(Membership::Join),
349 "invite" => Some(Membership::Invite),
350 "leave" => Some(Membership::Leave),
351 "ban" => Some(Membership::Ban),
352 _ => None,
353 }
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358pub enum ReceiptType {
359 Read,
360 ReadPrivate,
361}
362
363impl ReceiptType {
364 pub fn as_str(self) -> &'static str {
365 match self {
366 ReceiptType::Read => "m.read",
367 ReceiptType::ReadPrivate => "m.read.private",
368 }
369 }
370
371 pub fn from_wire_name(s: &str) -> Option<Self> {
372 match s {
373 "m.read" => Some(ReceiptType::Read),
374 "m.read.private" => Some(ReceiptType::ReadPrivate),
375 _ => None,
376 }
377 }
378}
379
380fn decode_enum<T>(idx: usize, column: &'static str, raw: &str, parse: fn(&str) -> Option<T>) -> rusqlite::Result<T> {
386 parse(raw).ok_or_else(|| rusqlite::Error::InvalidColumnType(idx, column.to_string(), rusqlite::types::Type::Text))
387}
388
389pub fn create_matrix_schema(conn: &Connection) -> rusqlite::Result<()> {
408 crate::schema_guard::enter(conn)?;
409 create_matrix_schema_tables(conn)?;
410 crate::schema_guard::leave(conn)
411}
412
413fn create_matrix_schema_tables(conn: &Connection) -> rusqlite::Result<()> {
414 conn.execute_batch(
415 r#"
416 -- Global stream ordering — see this module's doc comment on the
417 -- single-writer guarantee that makes `UPDATE ... RETURNING` safe.
418 CREATE TABLE IF NOT EXISTS stream_counter (
419 id INTEGER PRIMARY KEY CHECK (id = 1),
420 value INTEGER NOT NULL
421 );
422 INSERT OR IGNORE INTO stream_counter (id, value) VALUES (1, 0);
423
424 -- Federation F0: this server's signing keys and the verify keys
425 -- cached from remote servers. Secrets live only in the (encrypted) DB.
426 CREATE TABLE IF NOT EXISTS fed_signing_keys (
427 key_id TEXT PRIMARY KEY,
428 secret BLOB NOT NULL,
429 created_ms INTEGER NOT NULL,
430 retired_ms INTEGER
431 );
432 CREATE TABLE IF NOT EXISTS fed_remote_keys (
433 server_name TEXT NOT NULL,
434 key_id TEXT NOT NULL,
435 public_key TEXT NOT NULL,
436 valid_until_ms INTEGER NOT NULL,
437 fetched_ms INTEGER NOT NULL,
438 PRIMARY KEY (server_name, key_id)
439 );
440
441 -- user_id -> mxid, filled on first touch by ensure_matrix_user
442 -- (plan §2 manager decision: new table, not in the original DDL
443 -- text). public_id is immutable, so this mapping never changes.
444 CREATE TABLE IF NOT EXISTS matrix_users (
445 user_id INTEGER PRIMARY KEY,
446 mxid TEXT NOT NULL UNIQUE,
447 created_at TEXT NOT NULL,
448 nick TEXT
449 );
450 CREATE UNIQUE INDEX IF NOT EXISTS idx_matrix_users_nick_lower
451 ON matrix_users(LOWER(nick)) WHERE nick IS NOT NULL;
452
453 -- Nick belongs to a session, not to matrix_users and not to the
454 -- device. device_id is only a mark. One device may have many sessions.
455 -- matrix_users.nick stays for old databases and is not read.
456 CREATE TABLE IF NOT EXISTS messenger_sessions (
457 session_id TEXT PRIMARY KEY,
458 user_id INTEGER NOT NULL,
459 device_id TEXT NOT NULL,
460 nick TEXT NOT NULL
461 );
462 CREATE INDEX IF NOT EXISTS idx_messenger_sessions_user
463 ON messenger_sessions(user_id);
464 CREATE UNIQUE INDEX IF NOT EXISTS idx_messenger_sessions_nick_lower
465 ON messenger_sessions(LOWER(nick));
466
467 CREATE TABLE IF NOT EXISTS rooms (
468 id TEXT PRIMARY KEY,
469 kind TEXT NOT NULL,
470 room_version TEXT NOT NULL DEFAULT '11',
471 creator_user_id INTEGER NOT NULL,
472 created_at TEXT NOT NULL,
473 is_encrypted INTEGER NOT NULL DEFAULT 0,
474 join_rule TEXT NOT NULL DEFAULT 'invite',
475 history_visibility TEXT NOT NULL DEFAULT 'shared',
476 dm_pair_key TEXT UNIQUE,
477 legacy_dm_id INTEGER UNIQUE
478 );
479 CREATE INDEX IF NOT EXISTS idx_rooms_kind ON rooms(kind);
480
481 CREATE TABLE IF NOT EXISTS events (
482 stream_id INTEGER PRIMARY KEY,
483 event_id TEXT NOT NULL UNIQUE,
484 room_id TEXT NOT NULL REFERENCES rooms(id),
485 sender_user_id INTEGER NOT NULL,
486 event_type TEXT NOT NULL,
487 state_key TEXT,
488 content TEXT NOT NULL,
489 origin_server_ts INTEGER NOT NULL,
490 txn_id TEXT,
491 redacts TEXT REFERENCES events(event_id),
492 redacted_by TEXT REFERENCES events(event_id)
493 );
494 CREATE INDEX IF NOT EXISTS idx_events_room_stream ON events(room_id, stream_id);
495 CREATE INDEX IF NOT EXISTS idx_events_room_type_state ON events(room_id, event_type, state_key);
496 CREATE INDEX IF NOT EXISTS idx_events_sender ON events(sender_user_id, stream_id);
497
498 CREATE TABLE IF NOT EXISTS current_state (
499 room_id TEXT NOT NULL REFERENCES rooms(id),
500 event_type TEXT NOT NULL,
501 state_key TEXT NOT NULL,
502 event_id TEXT NOT NULL REFERENCES events(event_id),
503 PRIMARY KEY (room_id, event_type, state_key)
504 );
505
506 CREATE TABLE IF NOT EXISTS room_members (
507 room_id TEXT NOT NULL REFERENCES rooms(id),
508 user_id INTEGER NOT NULL,
509 membership TEXT NOT NULL,
510 power_level INTEGER,
511 updated_at TEXT NOT NULL,
512 PRIMARY KEY (room_id, user_id)
513 );
514 CREATE INDEX IF NOT EXISTS idx_room_members_user ON room_members(user_id, membership);
515
516 CREATE TABLE IF NOT EXISTS relations (
517 event_id TEXT PRIMARY KEY REFERENCES events(event_id),
518 room_id TEXT NOT NULL REFERENCES rooms(id),
519 rel_type TEXT NOT NULL,
520 target_id TEXT NOT NULL REFERENCES events(event_id),
521 agg_key TEXT
522 );
523 CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_id, rel_type);
524
525 CREATE TABLE IF NOT EXISTS receipts (
526 room_id TEXT NOT NULL REFERENCES rooms(id),
527 user_id INTEGER NOT NULL,
528 receipt_type TEXT NOT NULL,
529 event_id TEXT NOT NULL REFERENCES events(event_id),
530 ts INTEGER NOT NULL,
531 stream_id INTEGER NOT NULL,
532 PRIMARY KEY (room_id, user_id, receipt_type)
533 );
534 CREATE INDEX IF NOT EXISTS idx_receipts_room_stream ON receipts(room_id, stream_id);
535
536 -- DEVIATION from plan §2's literal text: room_id has no
537 -- `REFERENCES rooms(id)` — see this function's doc comment.
538 CREATE TABLE IF NOT EXISTS account_data (
539 user_id INTEGER NOT NULL,
540 room_id TEXT NOT NULL DEFAULT '',
541 data_type TEXT NOT NULL,
542 content TEXT NOT NULL,
543 stream_id INTEGER NOT NULL,
544 PRIMARY KEY (user_id, room_id, data_type)
545 );
546 CREATE INDEX IF NOT EXISTS idx_account_data_user_stream ON account_data(user_id, stream_id);
547
548 CREATE TABLE IF NOT EXISTS txn_dedup (
549 user_id INTEGER NOT NULL,
550 device_id TEXT NOT NULL,
551 txn_id TEXT NOT NULL,
552 event_id TEXT REFERENCES events(event_id),
553 created_at TEXT NOT NULL,
554 PRIMARY KEY (user_id, device_id, txn_id)
555 );
556
557 CREATE TABLE IF NOT EXISTS filters (
558 id INTEGER PRIMARY KEY AUTOINCREMENT,
559 user_id INTEGER NOT NULL,
560 definition TEXT NOT NULL
561 );
562
563 -- legacy_dm_message_map removed (M2); drop_legacy_dm_scaffold_if_empty cleans old DBs
564 "#,
565 )?;
566 crate::public_channels::create_public_schema(conn)?;
568 crate::public_forum::create_forum_schema(conn)?;
569 crate::media::create_media_schema(conn)?;
570 crate::fed_rooms::create_fed_schema(conn)?;
571 crate::dag_schema::create_dag_schema(conn)?;
572 crate::http::extras::create_schema(conn)?;
573 crate::http::presence::create_schema(conn)?;
574 crate::http::spec_rest::create_schema(conn)?;
575 crate::identities::create_identities_schema(conn)
576}
577
578pub fn parse_db_key(key_hex: &str) -> Result<[u8; 32], String> {
580 let h = key_hex.trim();
581 if h.len() != 64 || !h.bytes().all(|b| b.is_ascii_hexdigit()) {
582 return Err("database key must be 64 hex characters (32 bytes)".into());
583 }
584 let mut key = [0u8; 32];
585 for (i, b) in key.iter_mut().enumerate() {
586 *b = u8::from_str_radix(&h[2 * i..2 * i + 2], 16).map_err(|_| "database key must be hex".to_string())?;
587 }
588 Ok(key)
589}
590
591pub fn messenger_db_config(path: &str, key_hex: &str) -> Result<tesserax_store::DbConfig, String> {
593 let key = parse_db_key(key_hex)?;
594 Ok(tesserax_store::DbConfig::encrypted_native(path, std::sync::Arc::new(tesserax_store::keysource::StaticKeySource(key))))
595}
596
597pub fn open_messenger_db(path: &str, key_hex: &str) -> Result<tesserax_store::Db, String> {
600 let cfg = messenger_db_config(path, key_hex)?;
601 let db = tesserax_store::Db::open(&cfg).map_err(|e| e.to_string())?;
602 db.blocking(|conn| ensure_schema(conn)).map_err(|e| e.to_string())?;
603 Ok(db)
604}
605
606pub fn open_read_pool(path: &str, key_hex: &str, size: usize) -> Result<tesserax_store::ReadPool, String> {
608 let cfg = messenger_db_config(path, key_hex)?;
609 tesserax_store::ReadPoolConfig::from_config(cfg).pool_size(size.max(1)).open().map_err(|e| e.to_string())
610}
611
612pub fn ensure_schema(conn: &Connection) -> rusqlite::Result<()> {
614 create_matrix_schema(conn)?;
615 crate::keys::create_matrix_keys_schema(conn)?;
616 crate::retention::create_retention_schema(conn)?;
617 crate::public_channels::create_public_schema(conn)?;
618 Ok(())
619}
620
621pub(crate) fn next_stream_id(tx: &Transaction) -> rusqlite::Result<i64> {
633 tx.query_row("UPDATE stream_counter SET value = value + 1 WHERE id = 1 RETURNING value", [], |row| row.get(0))
634}
635
636pub fn max_stream_id(conn: &Connection) -> rusqlite::Result<i64> {
640 conn.query_row("SELECT value FROM stream_counter WHERE id = 1", [], |row| row.get(0))
641}
642
643pub fn ensure_matrix_user(conn: &Connection, user_id: i64, public_id: &str, now: &str) -> Result<String, MatrixStoreError> {
651 if is_reserved_localpart(public_id) {
652 return Err(MatrixStoreError::ReservedLocalpart);
653 }
654 let mxid = mxid_for_public_id(public_id);
655 conn.execute(
656 "INSERT INTO matrix_users (user_id, mxid, created_at) VALUES (?1, ?2, ?3)
657 ON CONFLICT(user_id) DO NOTHING",
658 params![user_id, mxid, now],
659 )?;
660 Ok(mxid)
661}
662
663pub fn mxid_of(conn: &Connection, user_id: i64) -> rusqlite::Result<Option<String>> {
665 conn.query_row("SELECT mxid FROM matrix_users WHERE user_id = ?1", params![user_id], |row| row.get(0))
666 .optional()
667}
668
669pub fn user_id_of(conn: &Connection, mxid: &str) -> rusqlite::Result<Option<i64>> {
671 conn.query_row("SELECT user_id FROM matrix_users WHERE mxid = ?1", params![mxid], |row| row.get(0))
672 .optional()
673}
674
675#[derive(Debug, Clone, PartialEq)]
680pub struct Room {
681 pub id: String,
682 pub kind: RoomKind,
683 pub room_version: String,
684 pub creator_user_id: i64,
685 pub created_at: String,
686 pub is_encrypted: bool,
687 pub join_rule: JoinRule,
688 pub history_visibility: HistoryVisibility,
689 pub dm_pair_key: Option<String>,
690 pub legacy_dm_id: Option<i64>,
691}
692
693const ROOM_SELECT_COLUMNS: &str =
694 "id, kind, room_version, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id";
695
696fn room_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Room> {
697 let kind_raw: String = row.get(1)?;
698 let join_rule_raw: String = row.get(6)?;
699 let history_visibility_raw: String = row.get(7)?;
700 Ok(Room {
701 id: row.get(0)?,
702 kind: decode_enum(1, "kind", &kind_raw, RoomKind::from_wire_name)?,
703 room_version: row.get(2)?,
704 creator_user_id: row.get(3)?,
705 created_at: row.get(4)?,
706 is_encrypted: row.get(5)?,
707 join_rule: decode_enum(6, "join_rule", &join_rule_raw, JoinRule::from_wire_name)?,
708 history_visibility: decode_enum(7, "history_visibility", &history_visibility_raw, HistoryVisibility::from_wire_name)?,
709 dm_pair_key: row.get(8)?,
710 legacy_dm_id: row.get(9)?,
711 })
712}
713
714#[allow(clippy::too_many_arguments)]
721fn insert_room_row(
722 conn: &Connection,
723 room_id: &str,
724 kind: RoomKind,
725 creator_user_id: i64,
726 created_at: &str,
727 is_encrypted: bool,
728 join_rule: JoinRule,
729 history_visibility: HistoryVisibility,
730 dm_pair_key: Option<&str>,
731 legacy_dm_id: Option<i64>,
732) -> rusqlite::Result<()> {
733 conn.execute(
734 "INSERT INTO rooms (id, kind, room_version, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id)
735 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
736 params![
737 room_id,
738 kind.as_str(),
739 MATRIX_ROOM_VERSION,
740 creator_user_id,
741 created_at,
742 is_encrypted,
743 join_rule.as_str(),
744 history_visibility.as_str(),
745 dm_pair_key,
746 legacy_dm_id,
747 ],
748 )?;
749 Ok(())
750}
751
752#[allow(clippy::too_many_arguments)]
759pub fn create_room(
760 conn: &Connection,
761 room_id: &str,
762 kind: RoomKind,
763 creator_user_id: i64,
764 created_at: &str,
765 is_encrypted: bool,
766 join_rule: JoinRule,
767 history_visibility: HistoryVisibility,
768 dm_pair_key: Option<&str>,
769 legacy_dm_id: Option<i64>,
770) -> rusqlite::Result<()> {
771 insert_room_row(conn, room_id, kind, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id)
772}
773
774pub fn get_room(conn: &Connection, room_id: &str) -> rusqlite::Result<Option<Room>> {
775 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE id = ?1"), params![room_id], room_from_row)
776 .optional()
777}
778
779pub fn room_by_dm_pair_key(conn: &Connection, pair_key: &str) -> rusqlite::Result<Option<Room>> {
784 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE dm_pair_key = ?1"), params![pair_key], room_from_row)
785 .optional()
786}
787
788pub fn room_by_legacy_dm_id(conn: &Connection, legacy_dm_id: i64) -> rusqlite::Result<Option<Room>> {
793 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE legacy_dm_id = ?1"), params![legacy_dm_id], room_from_row)
794 .optional()
795}
796
797pub fn clear_dm_pair_key(conn: &Connection, room_id: &str) -> rusqlite::Result<()> {
803 conn.execute("UPDATE rooms SET dm_pair_key = NULL WHERE id = ?1", params![room_id])?;
804 Ok(())
805}
806
807#[derive(Debug, Clone, PartialEq)]
812pub struct MatrixEvent {
813 pub stream_id: i64,
814 pub event_id: String,
815 pub room_id: String,
816 pub sender_user_id: i64,
817 pub event_type: String,
818 pub state_key: Option<String>,
820 pub content: String,
821 pub origin_server_ts: i64,
822 pub txn_id: Option<String>,
823 pub redacts: Option<String>,
824 pub redacted_by: Option<String>,
825}
826
827const EVENT_SELECT_COLUMNS: &str =
828 "stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id, redacts, redacted_by";
829
830const EVENT_SELECT_COLUMNS_ALIASED: &str = "e.stream_id, e.event_id, e.room_id, e.sender_user_id, e.event_type, e.state_key, e.content, e.origin_server_ts, e.txn_id, e.redacts, e.redacted_by";
831
832fn event_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<MatrixEvent> {
833 Ok(MatrixEvent {
834 stream_id: row.get(0)?,
835 event_id: row.get(1)?,
836 room_id: row.get(2)?,
837 sender_user_id: row.get(3)?,
838 event_type: row.get(4)?,
839 state_key: row.get(5)?,
840 content: row.get(6)?,
841 origin_server_ts: row.get(7)?,
842 txn_id: row.get(8)?,
843 redacts: row.get(9)?,
844 redacted_by: row.get(10)?,
845 })
846}
847
848fn collect_events(rows: &mut rusqlite::Rows<'_>) -> rusqlite::Result<Vec<MatrixEvent>> {
849 let mut out = Vec::new();
850 while let Some(row) = rows.next()? {
851 out.push(event_from_row(row)?);
852 }
853 Ok(out)
854}
855
856#[derive(Debug, Clone, Copy)]
860pub(crate) struct TimelineEventRow<'a> {
861 pub(crate) event_id: &'a str,
862 pub(crate) room_id: &'a str,
863 pub(crate) sender_user_id: i64,
864 pub(crate) event_type: &'a str,
865 pub(crate) content: &'a str,
866 pub(crate) origin_server_ts: i64,
867 pub(crate) txn_id: Option<&'a str>,
868}
869
870fn insert_timeline_event_in_tx(tx: &Transaction, row: &TimelineEventRow<'_>) -> Result<MatrixEvent, MatrixStoreError> {
879 #[cfg(feature = "f3-hash-ids")]
882 if let Some(p) = crate::f3::prepare_local(tx, row.room_id, row.sender_user_id, row.event_type, None, row.content, row.origin_server_ts)? {
883 let row = TimelineEventRow { event_id: &p.event_id, content: &p.content, ..*row };
884 return insert_timeline_event_raw_in_tx(tx, &row);
885 }
886 insert_timeline_event_raw_in_tx(tx, row)
887}
888
889pub(crate) fn insert_timeline_event_raw_in_tx(tx: &Transaction, row: &TimelineEventRow<'_>) -> Result<MatrixEvent, MatrixStoreError> {
891 let TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id } = *row;
892 let stream_id = next_stream_id(tx)?;
893 tx.execute(
894 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id)
895 VALUES (?1, ?2, ?3, ?4, ?5, NULL, ?6, ?7, ?8)",
896 params![stream_id, event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id],
897 )?;
898 populate_relations(tx, event_id, room_id, sender_user_id, content)?;
899 Ok(MatrixEvent {
900 stream_id,
901 event_id: event_id.to_string(),
902 room_id: room_id.to_string(),
903 sender_user_id,
904 event_type: event_type.to_string(),
905 state_key: None,
906 content: content.to_string(),
907 origin_server_ts,
908 txn_id: txn_id.map(str::to_string),
909 redacts: None,
910 redacted_by: None,
911 })
912}
913
914pub fn insert_timeline_event(
923 conn: &mut Connection,
924 event_id: &str,
925 room_id: &str,
926 sender_user_id: i64,
927 event_type: &str,
928 content: &str,
929 origin_server_ts: i64,
930) -> Result<MatrixEvent, MatrixStoreError> {
931 let tx = conn.transaction()?;
932 let row = TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id: None };
933 let event = insert_timeline_event_in_tx(&tx, &row)?;
934 tx.commit()?;
935 Ok(event)
936}
937
938#[derive(Debug, Clone, PartialEq)]
944pub enum DedupedWrite {
945 New(MatrixEvent),
946 Existing(MatrixEvent),
947}
948
949#[allow(clippy::too_many_arguments)]
959pub fn insert_timeline_event_deduped(
960 conn: &mut Connection,
961 device_id: &str,
962 txn_id: &str,
963 event_id: &str,
964 room_id: &str,
965 sender_user_id: i64,
966 event_type: &str,
967 content: &str,
968 origin_server_ts: i64,
969 now: &str,
970) -> Result<DedupedWrite, MatrixStoreError> {
971 let tx = conn.transaction()?;
972 if let TxnDedupEntry::Seen(existing_event_id) = txn_dedup_lookup(&tx, sender_user_id, device_id, txn_id)? {
973 let existing_event_id = existing_event_id.ok_or_else(|| MatrixStoreError::UnknownEventId(txn_id.to_string()))?;
974 let event = get_event(&tx, &existing_event_id)?.ok_or_else(|| MatrixStoreError::UnknownEventId(existing_event_id.clone()))?;
975 tx.commit()?;
976 return Ok(DedupedWrite::Existing(event));
977 }
978 let row = TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id: Some(txn_id) };
979 let event = insert_timeline_event_in_tx(&tx, &row)?;
980 txn_dedup_record(&tx, sender_user_id, device_id, txn_id, Some(&event.event_id), now)?;
981 tx.commit()?;
982 Ok(DedupedWrite::New(event))
983}
984
985#[allow(clippy::too_many_arguments)]
993fn apply_state_event_in_tx(
994 tx: &Transaction,
995 event_id: &str,
996 room_id: &str,
997 sender_user_id: i64,
998 event_type: &str,
999 state_key: &str,
1000 content: &str,
1001 origin_server_ts: i64,
1002 now: &str,
1003) -> Result<MatrixEvent, MatrixStoreError> {
1004 #[cfg(feature = "f3-hash-ids")]
1007 if let Some(p) = crate::f3::prepare_local(tx, room_id, sender_user_id, event_type, Some(state_key), content, origin_server_ts)? {
1008 return apply_state_event_raw_in_tx(tx, &p.event_id, room_id, sender_user_id, event_type, state_key, &p.content, origin_server_ts, now);
1009 }
1010 apply_state_event_raw_in_tx(tx, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, now)
1011}
1012
1013#[allow(clippy::too_many_arguments)]
1015pub(crate) fn apply_state_event_raw_in_tx(
1016 tx: &Transaction,
1017 event_id: &str,
1018 room_id: &str,
1019 sender_user_id: i64,
1020 event_type: &str,
1021 state_key: &str,
1022 content: &str,
1023 origin_server_ts: i64,
1024 now: &str,
1025) -> Result<MatrixEvent, MatrixStoreError> {
1026 let stream_id = next_stream_id(tx)?;
1027 tx.execute(
1028 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id)
1029 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL)",
1030 params![stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts],
1031 )?;
1032 tx.execute(
1033 "INSERT INTO current_state (room_id, event_type, state_key, event_id) VALUES (?1, ?2, ?3, ?4)
1034 ON CONFLICT(room_id, event_type, state_key) DO UPDATE SET event_id = excluded.event_id",
1035 params![room_id, event_type, state_key, event_id],
1036 )?;
1037 if event_type == "m.room.member" {
1038 refresh_room_member(tx, room_id, state_key, content, now)?;
1039 }
1040 if event_type == "m.room.power_levels" {
1041 refresh_power_levels(tx, room_id, content)?;
1042 }
1043 Ok(MatrixEvent {
1044 stream_id,
1045 event_id: event_id.to_string(),
1046 room_id: room_id.to_string(),
1047 sender_user_id,
1048 event_type: event_type.to_string(),
1049 state_key: Some(state_key.to_string()),
1050 content: content.to_string(),
1051 origin_server_ts,
1052 txn_id: None,
1053 redacts: None,
1054 redacted_by: None,
1055 })
1056}
1057
1058#[cfg_attr(not(feature = "f3-hash-ids"), allow(dead_code))]
1061#[allow(clippy::too_many_arguments)]
1062pub(crate) fn insert_past_state_row(tx: &Transaction, event_id: &str, room_id: &str, sender_user_id: i64, event_type: &str, state_key: &str, content: &str, origin_server_ts: i64) -> Result<(), MatrixStoreError> {
1063 let stream_id = next_stream_id(tx)?;
1064 tx.execute(
1065 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL)",
1066 params![stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts],
1067 )?;
1068 Ok(())
1069}
1070
1071#[cfg_attr(not(feature = "f3-hash-ids"), allow(dead_code))]
1075pub(crate) fn set_current_state_slot(tx: &Transaction, room_id: &str, event_type: &str, state_key: &str, event_id: &str, now: &str) -> Result<(), MatrixStoreError> {
1076 let content: String = tx.query_row("SELECT content FROM events WHERE event_id = ?1", params![event_id], |r| r.get(0))?;
1077 tx.execute(
1078 "INSERT INTO current_state (room_id, event_type, state_key, event_id) VALUES (?1, ?2, ?3, ?4)
1079 ON CONFLICT(room_id, event_type, state_key) DO UPDATE SET event_id = excluded.event_id",
1080 params![room_id, event_type, state_key, event_id],
1081 )?;
1082 if event_type == "m.room.member" {
1083 refresh_room_member(tx, room_id, state_key, &content, now)?;
1084 }
1085 if event_type == "m.room.power_levels" {
1086 refresh_power_levels(tx, room_id, &content)?;
1087 }
1088 Ok(())
1089}
1090
1091#[derive(Debug, Clone, Copy)]
1096pub struct StateEventWrite<'a> {
1097 pub event_id: &'a str,
1098 pub room_id: &'a str,
1099 pub sender_user_id: i64,
1100 pub event_type: &'a str,
1101 pub state_key: &'a str,
1102 pub content: &'a str,
1103 pub origin_server_ts: i64,
1104 pub now: &'a str,
1105}
1106
1107pub fn apply_state_event(conn: &mut Connection, write: &StateEventWrite<'_>) -> Result<MatrixEvent, MatrixStoreError> {
1112 let tx = conn.transaction()?;
1113 let event = apply_state_event_in_tx(
1114 &tx,
1115 write.event_id,
1116 write.room_id,
1117 write.sender_user_id,
1118 write.event_type,
1119 write.state_key,
1120 write.content,
1121 write.origin_server_ts,
1122 write.now,
1123 )?;
1124 tx.commit()?;
1125 Ok(event)
1126}
1127
1128#[derive(Debug, Clone, Default, PartialEq, Eq)]
1130pub struct DisplaynameRefresh {
1131 pub rooms_updated: usize,
1133 pub affected_user_ids: HashSet<i64>,
1136}
1137
1138pub fn refresh_member_displayname(
1156 conn: &mut Connection,
1157 user_id: i64,
1158 displayname: &str,
1159 now: &str,
1160 origin_server_ts: i64,
1161) -> Result<DisplaynameRefresh, MatrixStoreError> {
1162 let mut outcome = DisplaynameRefresh::default();
1163 if displayname.is_empty() {
1164 return Ok(outcome);
1165 }
1166 let Some(mxid) = mxid_of(conn, user_id)? else {
1167 return Ok(outcome);
1168 };
1169
1170 let tx = conn.transaction()?;
1171 for membership in [Membership::Join, Membership::Invite] {
1172 for room_id in rooms_for_user(&tx, user_id, Some(membership))? {
1173 let Some(current) = current_state_event(&tx, &room_id, "m.room.member", &mxid)? else {
1174 continue;
1175 };
1176 let mut content: serde_json::Value = serde_json::from_str(¤t.content)?;
1177 if content.get("displayname").and_then(|v| v.as_str()) == Some(displayname) {
1178 continue;
1179 }
1180 let Some(fields) = content.as_object_mut() else {
1181 continue;
1182 };
1183 fields.insert("displayname".to_string(), serde_json::Value::String(displayname.to_string()));
1184
1185 let sender_user_id = if membership == Membership::Join { user_id } else { current.sender_user_id };
1186 apply_state_event_in_tx(
1187 &tx,
1188 &new_event_id(),
1189 &room_id,
1190 sender_user_id,
1191 "m.room.member",
1192 &mxid,
1193 &content.to_string(),
1194 origin_server_ts,
1195 now,
1196 )?;
1197 outcome.rooms_updated += 1;
1198 outcome.affected_user_ids.insert(user_id);
1199 for member in room_members(&tx, &room_id, None)? {
1200 if matches!(member.membership, Membership::Join | Membership::Invite) {
1201 outcome.affected_user_ids.insert(member.user_id);
1202 }
1203 }
1204 }
1205 }
1206 tx.commit()?;
1207 Ok(outcome)
1208}
1209
1210pub fn matrix_user_ids(conn: &Connection) -> rusqlite::Result<Vec<i64>> {
1214 let mut stmt = conn.prepare("SELECT user_id FROM matrix_users ORDER BY user_id")?;
1215 let rows = stmt.query_map([], |row| row.get(0))?;
1216 rows.collect()
1217}
1218
1219#[derive(Debug, Clone, PartialEq)]
1229pub struct NewStateEvent {
1230 pub event_id: String,
1231 pub sender_user_id: i64,
1232 pub event_type: String,
1233 pub state_key: String,
1234 pub content: String,
1235}
1236
1237#[derive(Debug, Clone, Copy)]
1243pub struct RoomBootstrap<'a> {
1244 pub room_id: &'a str,
1245 pub kind: RoomKind,
1246 pub creator_user_id: i64,
1247 pub created_at: &'a str,
1248 pub is_encrypted: bool,
1249 pub join_rule: JoinRule,
1250 pub history_visibility: HistoryVisibility,
1251 pub dm_pair_key: Option<&'a str>,
1252 pub legacy_dm_id: Option<i64>,
1253}
1254
1255pub fn create_room_with_state(
1266 conn: &mut Connection,
1267 bootstrap: RoomBootstrap<'_>,
1268 state_events: &[NewStateEvent],
1269 origin_server_ts: i64,
1270) -> Result<(Room, Vec<MatrixEvent>), MatrixStoreError> {
1271 let tx = conn.transaction()?;
1272 insert_room_row(
1273 &tx,
1274 bootstrap.room_id,
1275 bootstrap.kind,
1276 bootstrap.creator_user_id,
1277 bootstrap.created_at,
1278 bootstrap.is_encrypted,
1279 bootstrap.join_rule,
1280 bootstrap.history_visibility,
1281 bootstrap.dm_pair_key,
1282 bootstrap.legacy_dm_id,
1283 )?;
1284 #[cfg(feature = "f3-hash-ids")]
1287 if !(bootstrap.kind == RoomKind::Channel && !bootstrap.is_encrypted) {
1288 crate::f3::mark_room(&tx, bootstrap.room_id)?;
1289 }
1290
1291 let mut applied = Vec::with_capacity(state_events.len());
1292 for event in state_events {
1293 applied.push(apply_state_event_in_tx(
1294 &tx,
1295 &event.event_id,
1296 bootstrap.room_id,
1297 event.sender_user_id,
1298 &event.event_type,
1299 &event.state_key,
1300 &event.content,
1301 origin_server_ts,
1302 bootstrap.created_at,
1303 )?);
1304 }
1305 tx.commit()?;
1306
1307 Ok((
1308 Room {
1309 id: bootstrap.room_id.to_string(),
1310 kind: bootstrap.kind,
1311 room_version: MATRIX_ROOM_VERSION.to_string(),
1312 creator_user_id: bootstrap.creator_user_id,
1313 created_at: bootstrap.created_at.to_string(),
1314 is_encrypted: bootstrap.is_encrypted,
1315 join_rule: bootstrap.join_rule,
1316 history_visibility: bootstrap.history_visibility,
1317 dm_pair_key: bootstrap.dm_pair_key.map(str::to_string),
1318 legacy_dm_id: bootstrap.legacy_dm_id,
1319 },
1320 applied,
1321 ))
1322}
1323
1324pub(crate) fn refresh_room_member(tx: &Transaction, room_id: &str, state_key: &str, content: &str, now: &str) -> Result<(), MatrixStoreError> {
1332 let value: serde_json::Value = serde_json::from_str(content)?;
1333 let membership_str = value
1334 .get("membership")
1335 .and_then(|v| v.as_str())
1336 .ok_or_else(|| MatrixStoreError::InvalidMembership("missing 'membership' field".to_string()))?;
1337 let membership =
1338 Membership::from_wire_name(membership_str).ok_or_else(|| MatrixStoreError::InvalidMembership(membership_str.to_string()))?;
1339 let user_id = user_id_of(tx, state_key)?.ok_or_else(|| MatrixStoreError::UnknownMxid(state_key.to_string()))?;
1340 tx.execute(
1341 "INSERT INTO room_members (room_id, user_id, membership, power_level, updated_at)
1342 VALUES (?1, ?2, ?3, NULL, ?4)
1343 ON CONFLICT(room_id, user_id) DO UPDATE SET membership = excluded.membership, updated_at = excluded.updated_at",
1344 params![room_id, user_id, membership.as_str(), now],
1345 )?;
1346 Ok(())
1347}
1348
1349pub(crate) fn refresh_power_levels(tx: &Transaction, room_id: &str, content: &str) -> Result<(), MatrixStoreError> {
1358 let value: serde_json::Value = serde_json::from_str(content)?;
1359 tx.execute("UPDATE room_members SET power_level = NULL WHERE room_id = ?1", params![room_id])?;
1360 if let Some(users) = value.get("users").and_then(|v| v.as_object()) {
1361 for (mxid, level) in users {
1362 let Some(level) = level.as_i64() else { continue };
1363 let Some(user_id) = user_id_of(tx, mxid)? else { continue };
1364 tx.execute(
1365 "UPDATE room_members SET power_level = ?1 WHERE room_id = ?2 AND user_id = ?3",
1366 params![level, room_id, user_id],
1367 )?;
1368 }
1369 }
1370 Ok(())
1371}
1372
1373#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1385pub enum PowerAction {
1386 Invite,
1387 Kick,
1388 Ban,
1389 Redact,
1390 StateDefault,
1391}
1392
1393impl PowerAction {
1394 fn field_and_default(self) -> (&'static str, i64) {
1395 match self {
1396 PowerAction::Invite => ("invite", 50),
1397 PowerAction::Kick => ("kick", 50),
1398 PowerAction::Ban => ("ban", 50),
1399 PowerAction::Redact => ("redact", 50),
1400 PowerAction::StateDefault => ("state_default", 50),
1401 }
1402 }
1403}
1404
1405pub fn user_level(power_levels: &serde_json::Value, mxid: &str) -> i64 {
1408 power_levels
1409 .get("users")
1410 .and_then(|users| users.get(mxid))
1411 .and_then(serde_json::Value::as_i64)
1412 .unwrap_or_else(|| power_levels.get("users_default").and_then(serde_json::Value::as_i64).unwrap_or(0))
1413}
1414
1415pub fn event_level(power_levels: &serde_json::Value, event_type: &str, is_state: bool) -> i64 {
1420 if let Some(level) = power_levels.get("events").and_then(|events| events.get(event_type)).and_then(serde_json::Value::as_i64) {
1421 return level;
1422 }
1423 let (key, default) = if is_state { ("state_default", 50) } else { ("events_default", 0) };
1424 power_levels.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default)
1425}
1426
1427pub fn can(power_levels: &serde_json::Value, action: PowerAction, mxid: &str) -> bool {
1431 let (field, default) = action.field_and_default();
1432 let required = power_levels.get(field).and_then(serde_json::Value::as_i64).unwrap_or(default);
1433 user_level(power_levels, mxid) >= required
1434}
1435
1436pub fn can_act_on(power_levels: &serde_json::Value, action: PowerAction, sender_mxid: &str, target_mxid: &str, self_leave: bool) -> bool {
1449 if self_leave && sender_mxid == target_mxid {
1450 return true;
1451 }
1452 can(power_levels, action, sender_mxid) && user_level(power_levels, sender_mxid) > user_level(power_levels, target_mxid)
1453}
1454
1455fn power_levels_scalar_default(key: &str) -> i64 {
1460 match key {
1461 "events_default" | "users_default" => 0,
1462 _ => 50,
1463 }
1464}
1465
1466const POWER_LEVELS_SCALAR_KEYS: [&str; 7] = ["ban", "kick", "redact", "invite", "state_default", "events_default", "users_default"];
1471
1472fn reject_if_either_side_exceeds(old_value: Option<i64>, new_value: Option<i64>, sender_level: i64) -> Result<(), &'static str> {
1478 if old_value != new_value && (old_value.is_some_and(|v| v > sender_level) || new_value.is_some_and(|v| v > sender_level)) {
1479 return Err("cannot change a power-level field at or above your own level");
1480 }
1481 Ok(())
1482}
1483
1484pub fn validate_power_levels_change(old: &serde_json::Value, new: &serde_json::Value, sender_mxid: &str) -> Result<(), &'static str> {
1508 let sender_level = user_level(old, sender_mxid);
1509
1510 for key in POWER_LEVELS_SCALAR_KEYS {
1511 let default = power_levels_scalar_default(key);
1512 let old_value = old.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default);
1513 let new_value = new.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default);
1514 reject_if_either_side_exceeds(Some(old_value), Some(new_value), sender_level)?;
1515 }
1516
1517 let old_events = old.get("events").and_then(serde_json::Value::as_object);
1518 let new_events = new.get("events").and_then(serde_json::Value::as_object);
1519 let mut event_type_keys: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
1520 if let Some(map) = old_events {
1521 event_type_keys.extend(map.keys().map(String::as_str));
1522 }
1523 if let Some(map) = new_events {
1524 event_type_keys.extend(map.keys().map(String::as_str));
1525 }
1526 for event_type in event_type_keys {
1527 let old_value = old_events.and_then(|m| m.get(event_type)).and_then(serde_json::Value::as_i64);
1528 let new_value = new_events.and_then(|m| m.get(event_type)).and_then(serde_json::Value::as_i64);
1529 reject_if_either_side_exceeds(old_value, new_value, sender_level)?;
1530 }
1531
1532 let old_notif_room = old.get("notifications").and_then(|v| v.get("room")).and_then(serde_json::Value::as_i64);
1533 let new_notif_room = new.get("notifications").and_then(|v| v.get("room")).and_then(serde_json::Value::as_i64);
1534 reject_if_either_side_exceeds(old_notif_room, new_notif_room, sender_level)?;
1535
1536 let old_users = old.get("users").and_then(serde_json::Value::as_object);
1537 let new_users = new.get("users").and_then(serde_json::Value::as_object);
1538 let mut user_keys: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
1539 if let Some(map) = old_users {
1540 user_keys.extend(map.keys().map(String::as_str));
1541 }
1542 if let Some(map) = new_users {
1543 user_keys.extend(map.keys().map(String::as_str));
1544 }
1545 for target_mxid in user_keys {
1546 let old_effective = user_level(old, target_mxid);
1547 let new_effective = user_level(new, target_mxid);
1548 if old_effective == new_effective {
1549 continue;
1550 }
1551 if target_mxid != sender_mxid && old_effective >= sender_level {
1552 return Err("cannot change the level of a user at or above your own level");
1553 }
1554 if new_effective > sender_level {
1555 return Err("cannot set a user's level above your own");
1556 }
1557 }
1558
1559 Ok(())
1560}
1561
1562pub fn stripped_state_json(conn: &Connection, event: &MatrixEvent) -> Result<serde_json::Value, MatrixStoreError> {
1580 let sender = mxid_of(conn, event.sender_user_id)?.unwrap_or_default();
1581 let content: serde_json::Value = serde_json::from_str(&event.content)?;
1582 Ok(serde_json::json!({
1583 "content": content,
1584 "state_key": event.state_key.clone().unwrap_or_default(),
1585 "type": event.event_type,
1586 "sender": sender,
1587 }))
1588}
1589
1590pub fn stripped_invite_state(conn: &Connection, room_id: &str, inviter_user_id: i64) -> Result<Vec<serde_json::Value>, MatrixStoreError> {
1601 let mut out = Vec::new();
1602 for event_type in ["m.room.create", "m.room.join_rules", "m.room.encryption", "m.room.name"] {
1603 if let Some(event) = current_state_event(conn, room_id, event_type, "")? {
1604 out.push(stripped_state_json(conn, &event)?);
1605 }
1606 }
1607 if let Some(inviter_mxid) = mxid_of(conn, inviter_user_id)? {
1608 if let Some(event) = current_state_event(conn, room_id, "m.room.member", &inviter_mxid)? {
1609 out.push(stripped_state_json(conn, &event)?);
1610 }
1611 }
1612 Ok(out)
1613}
1614
1615pub fn get_event(conn: &Connection, event_id: &str) -> rusqlite::Result<Option<MatrixEvent>> {
1616 let closed = conn
1617 .query_row(&format!("SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE event_id = ?1"), params![event_id], event_from_row)
1618 .optional()?;
1619 if closed.is_some() {
1620 return Ok(closed);
1621 }
1622 match crate::public_channels::get_event(conn, event_id) {
1624 Ok(found) => Ok(found),
1625 Err(rusqlite::Error::SqliteFailure(_, Some(msg))) if msg.contains("no such table") => Ok(None),
1626 Err(e) => Err(e),
1627 }
1628}
1629
1630pub fn current_state_event(conn: &Connection, room_id: &str, event_type: &str, state_key: &str) -> rusqlite::Result<Option<MatrixEvent>> {
1631 conn.query_row(
1632 &format!(
1633 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM current_state cs JOIN events e ON e.event_id = cs.event_id
1634 WHERE cs.room_id = ?1 AND cs.event_type = ?2 AND cs.state_key = ?3"
1635 ),
1636 params![room_id, event_type, state_key],
1637 event_from_row,
1638 )
1639 .optional()
1640}
1641
1642pub fn current_state_all(conn: &Connection, room_id: &str) -> rusqlite::Result<Vec<MatrixEvent>> {
1643 let mut stmt = conn.prepare(&format!(
1644 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM current_state cs JOIN events e ON e.event_id = cs.event_id WHERE cs.room_id = ?1"
1645 ))?;
1646 let mut rows = stmt.query(params![room_id])?;
1647 collect_events(&mut rows)
1648}
1649
1650pub fn state_events_of_type_at(conn: &Connection, room_id: &str, event_type: &str, at_stream_id: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1658 let mut stmt = conn.prepare(&format!(
1659 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1660 WHERE e.room_id = ?1 AND e.event_type = ?2 AND e.state_key IS NOT NULL AND e.stream_id <= ?3
1661 AND e.stream_id = (
1662 SELECT MAX(stream_id) FROM events e2
1663 WHERE e2.room_id = e.room_id AND e2.event_type = e.event_type AND e2.state_key = e.state_key AND e2.stream_id <= ?3
1664 )"
1665 ))?;
1666 let mut rows = stmt.query(params![room_id, event_type, at_stream_id])?;
1667 collect_events(&mut rows)
1668}
1669
1670pub fn member_state_changed_in_window(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1680 let mut stmt = conn.prepare(&format!(
1681 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1682 WHERE e.room_id = ?1 AND e.event_type = 'm.room.member' AND e.state_key IS NOT NULL
1683 AND e.stream_id = (
1684 SELECT MAX(e2.stream_id) FROM events e2
1685 WHERE e2.room_id = e.room_id AND e2.event_type = 'm.room.member' AND e2.state_key = e.state_key AND e2.stream_id <= ?3
1686 )
1687 AND e.stream_id > ?2"
1688 ))?;
1689 let mut rows = stmt.query(params![room_id, since_exclusive, upto_inclusive])?;
1690 collect_events(&mut rows)
1691}
1692
1693pub fn non_member_state_changed_in_window(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1702 let mut stmt = conn.prepare(&format!(
1703 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1704 WHERE e.room_id = ?1 AND e.event_type != 'm.room.member' AND e.state_key IS NOT NULL
1705 AND e.stream_id = (
1706 SELECT MAX(e2.stream_id) FROM events e2
1707 WHERE e2.room_id = e.room_id AND e2.event_type = e.event_type AND e2.state_key = e.state_key AND e2.stream_id <= ?3
1708 )
1709 AND e.stream_id > ?2"
1710 ))?;
1711 let mut rows = stmt.query(params![room_id, since_exclusive, upto_inclusive])?;
1712 collect_events(&mut rows)
1713}
1714
1715pub fn events_in_room_after(conn: &Connection, room_id: &str, since_stream: i64, limit: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1718 let mut stmt = conn.prepare(&format!(
1719 "SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE room_id = ?1 AND stream_id > ?2 ORDER BY stream_id ASC LIMIT ?3"
1720 ))?;
1721 let mut rows = stmt.query(params![room_id, since_stream, limit])?;
1722 let mut out = collect_events(&mut rows)?;
1723 if crate::public_channels::is_public_room(conn, room_id)? {
1724 out.extend(crate::public_channels::events_after(conn, room_id, since_stream, limit)?);
1725 out.sort_by_key(|e| e.stream_id);
1726 out.truncate(limit.max(0) as usize);
1727 }
1728 Ok(out)
1729}
1730
1731pub fn events_in_room_before(conn: &Connection, room_id: &str, before_stream: i64, limit: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1735 let mut stmt = conn.prepare(&format!(
1736 "SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE room_id = ?1 AND stream_id < ?2 ORDER BY stream_id DESC LIMIT ?3"
1737 ))?;
1738 let mut rows = stmt.query(params![room_id, before_stream, limit])?;
1739 let mut out = collect_events(&mut rows)?;
1740 if crate::public_channels::is_public_room(conn, room_id)? {
1741 out.extend(crate::public_channels::events_before(conn, room_id, before_stream, limit)?);
1742 out.sort_by_key(|e| std::cmp::Reverse(e.stream_id));
1743 out.truncate(limit.max(0) as usize);
1744 }
1745 Ok(out)
1746}
1747
1748fn populate_relations(tx: &Transaction, event_id: &str, room_id: &str, sender_user_id: i64, content: &str) -> Result<(), MatrixStoreError> {
1772 let Ok(value) = serde_json::from_str::<serde_json::Value>(content) else {
1773 return Ok(());
1774 };
1775 let Some(relates_to) = value.get("m.relates_to") else {
1776 return Ok(());
1777 };
1778
1779 let (rel_type, target_id, agg_key): (String, String, Option<String>) = if let Some(reply) = relates_to.get("m.in_reply_to") {
1780 match reply.get("event_id").and_then(|v| v.as_str()) {
1781 Some(target) => ("m.in_reply_to".to_string(), target.to_string(), None),
1782 None => return Ok(()),
1783 }
1784 } else {
1785 let rel_type = relates_to.get("rel_type").and_then(|v| v.as_str());
1786 let target = relates_to.get("event_id").and_then(|v| v.as_str());
1787 match (rel_type, target) {
1788 (Some(rt), Some(target)) => {
1789 let key = relates_to.get("key").and_then(|v| v.as_str()).map(str::to_string);
1790 (rt.to_string(), target.to_string(), key)
1791 }
1792 _ => return Ok(()),
1793 }
1794 };
1795
1796 let target_room: Option<String> = tx
1797 .query_row("SELECT room_id FROM events WHERE event_id = ?1", params![target_id], |row| row.get(0))
1798 .optional()?;
1799 match target_room {
1800 Some(ref found_room) if found_room == room_id => {}
1801 _ => return Err(MatrixStoreError::InvalidRelationTarget(target_id)),
1802 }
1803
1804 if rel_type == "m.annotation" {
1805 let duplicate: Option<i64> = tx
1806 .query_row(
1807 "SELECT 1 FROM relations r JOIN events e ON e.event_id = r.event_id
1808 WHERE r.target_id = ?1 AND r.rel_type = 'm.annotation' AND r.agg_key IS ?2 AND e.sender_user_id = ?3
1809 AND e.redacted_by IS NULL
1810 LIMIT 1",
1811 params![target_id, agg_key, sender_user_id],
1812 |row| row.get(0),
1813 )
1814 .optional()?;
1815 if duplicate.is_some() {
1816 return Err(MatrixStoreError::DuplicateAnnotation);
1817 }
1818 }
1819
1820 tx.execute(
1821 "INSERT INTO relations (event_id, room_id, rel_type, target_id, agg_key) VALUES (?1, ?2, ?3, ?4, ?5)",
1822 params![event_id, room_id, rel_type, target_id, agg_key],
1823 )?;
1824 Ok(())
1825}
1826
1827pub fn relations_of(
1833 conn: &Connection,
1834 target_event_id: &str,
1835 rel_type: Option<&str>,
1836 event_type: Option<&str>,
1837 before_stream: i64,
1838 limit: i64,
1839) -> rusqlite::Result<Vec<MatrixEvent>> {
1840 let mut sql = format!("SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM relations r JOIN events e ON e.event_id = r.event_id WHERE r.target_id = ? AND e.stream_id < ?");
1845 let mut values: Vec<&dyn rusqlite::ToSql> = vec![&target_event_id, &before_stream];
1846 if let Some(rt) = &rel_type {
1847 sql.push_str(" AND r.rel_type = ?");
1848 values.push(rt);
1849 }
1850 if let Some(et) = &event_type {
1851 sql.push_str(" AND e.event_type = ?");
1852 values.push(et);
1853 }
1854 sql.push_str(" ORDER BY e.stream_id DESC LIMIT ?");
1855 values.push(&limit);
1856
1857 let mut stmt = conn.prepare(&sql)?;
1858 let mut rows = stmt.query(values.as_slice())?;
1859 collect_events(&mut rows)
1860}
1861
1862fn redact_content_per_v11(event_type: &str, content: &str) -> Result<String, MatrixStoreError> {
1878 let value: serde_json::Value = serde_json::from_str(content)?;
1879 let obj = value.as_object().cloned().unwrap_or_default();
1880 let mut kept = serde_json::Map::new();
1881
1882 match event_type {
1883 "m.room.create" => kept = obj,
1884 "m.room.member" => {
1885 for key in ["membership", "join_authorised_via_users_server"] {
1886 if let Some(v) = obj.get(key) {
1887 kept.insert(key.to_string(), v.clone());
1888 }
1889 }
1890 if let Some(signed) = obj.get("third_party_invite").and_then(|v| v.get("signed")) {
1891 let mut third_party_invite = serde_json::Map::new();
1892 third_party_invite.insert("signed".to_string(), signed.clone());
1893 kept.insert("third_party_invite".to_string(), serde_json::Value::Object(third_party_invite));
1894 }
1895 }
1896 "m.room.join_rules" => {
1897 for key in ["join_rule", "allow"] {
1898 if let Some(v) = obj.get(key) {
1899 kept.insert(key.to_string(), v.clone());
1900 }
1901 }
1902 }
1903 "m.room.power_levels" => {
1904 for key in [
1905 "ban",
1906 "events",
1907 "events_default",
1908 "invite",
1909 "kick",
1910 "redact",
1911 "state_default",
1912 "users",
1913 "users_default",
1914 ] {
1915 if let Some(v) = obj.get(key) {
1916 kept.insert(key.to_string(), v.clone());
1917 }
1918 }
1919 }
1920 "m.room.history_visibility" => {
1921 if let Some(v) = obj.get("history_visibility") {
1922 kept.insert("history_visibility".to_string(), v.clone());
1923 }
1924 }
1925 _ => {}
1926 }
1927
1928 Ok(serde_json::Value::Object(kept).to_string())
1929}
1930
1931fn redact_event_in_tx(
1952 tx: &Transaction,
1953 room_id: &str,
1954 target_event_id: &str,
1955 redaction_event_id: &str,
1956 sender_user_id: i64,
1957 reason: Option<&str>,
1958 origin_server_ts: i64,
1959) -> Result<MatrixEvent, MatrixStoreError> {
1960 let target: Option<(String, String, String)> = tx
1961 .query_row(
1962 "SELECT event_type, content, room_id FROM events WHERE event_id = ?1",
1963 params![target_event_id],
1964 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
1965 )
1966 .optional()?;
1967 let (target_type, target_content, target_room) = target.ok_or_else(|| MatrixStoreError::UnknownEventId(target_event_id.to_string()))?;
1968 if target_room != room_id {
1969 return Err(MatrixStoreError::WrongRoom(target_event_id.to_string()));
1970 }
1971 if target_type == "m.room.create" || target_type == "m.room.encryption" {
1972 return Err(MatrixStoreError::UnredactableEvent(target_type));
1973 }
1974
1975 let stream_id = next_stream_id(tx)?;
1976 let mut redaction_content = serde_json::json!({ "redacts": target_event_id });
1979 if let Some(r) = reason {
1980 redaction_content["reason"] = serde_json::Value::String(r.to_string());
1981 }
1982 let redaction_content = redaction_content.to_string();
1983 tx.execute(
1984 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id, redacts)
1985 VALUES (?1, ?2, ?3, ?4, 'm.room.redaction', NULL, ?5, ?6, NULL, ?7)",
1986 params![stream_id, redaction_event_id, room_id, sender_user_id, redaction_content, origin_server_ts, target_event_id],
1987 )?;
1988
1989 let stripped_content = redact_content_per_v11(&target_type, &target_content)?;
1990 tx.execute(
1991 "UPDATE events SET content = ?1, redacted_by = ?2 WHERE event_id = ?3",
1992 params![stripped_content, redaction_event_id, target_event_id],
1993 )?;
1994
1995 Ok(MatrixEvent {
1996 stream_id,
1997 event_id: redaction_event_id.to_string(),
1998 room_id: room_id.to_string(),
1999 sender_user_id,
2000 event_type: "m.room.redaction".to_string(),
2001 state_key: None,
2002 content: redaction_content,
2003 origin_server_ts,
2004 txn_id: None,
2005 redacts: Some(target_event_id.to_string()),
2006 redacted_by: None,
2007 })
2008}
2009
2010pub fn redact_event(
2011 conn: &mut Connection,
2012 room_id: &str,
2013 target_event_id: &str,
2014 redaction_event_id: &str,
2015 sender_user_id: i64,
2016 reason: Option<&str>,
2017 origin_server_ts: i64,
2018) -> Result<MatrixEvent, MatrixStoreError> {
2019 let tx = conn.transaction()?;
2020 let event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
2021 tx.commit()?;
2022 Ok(event)
2023}
2024
2025#[derive(Debug, Clone, Copy)]
2028pub struct Redaction<'a> {
2029 pub room_id: &'a str,
2030 pub target_event_id: &'a str,
2031 pub redaction_event_id: &'a str,
2032 pub sender_user_id: i64,
2033 pub reason: Option<&'a str>,
2034 pub origin_server_ts: i64,
2035}
2036
2037pub fn redact_event_marked(
2047 conn: &mut Connection,
2048 redaction: &Redaction<'_>,
2049 extra_content: &serde_json::Value,
2050) -> Result<MatrixEvent, MatrixStoreError> {
2051 let Redaction { room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts } = *redaction;
2052 let tx = conn.transaction()?;
2053 let mut event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
2054 let mut content: serde_json::Value = serde_json::from_str(&event.content)?;
2055 if let (Some(content_obj), Some(extra_obj)) = (content.as_object_mut(), extra_content.as_object()) {
2056 for (key, value) in extra_obj {
2057 content_obj.insert(key.clone(), value.clone());
2058 }
2059 }
2060 let content_str = content.to_string();
2061 tx.execute("UPDATE events SET content = ?1 WHERE event_id = ?2", params![content_str, redaction_event_id])?;
2062 event.content = content_str;
2063 tx.commit()?;
2064 Ok(event)
2065}
2066
2067#[allow(clippy::too_many_arguments)]
2072pub fn redact_event_deduped(
2073 conn: &mut Connection,
2074 device_id: &str,
2075 txn_id: &str,
2076 room_id: &str,
2077 target_event_id: &str,
2078 redaction_event_id: &str,
2079 sender_user_id: i64,
2080 reason: Option<&str>,
2081 origin_server_ts: i64,
2082 now: &str,
2083) -> Result<DedupedWrite, MatrixStoreError> {
2084 let tx = conn.transaction()?;
2085 if let TxnDedupEntry::Seen(existing_event_id) = txn_dedup_lookup(&tx, sender_user_id, device_id, txn_id)? {
2086 let existing_event_id = existing_event_id.ok_or_else(|| MatrixStoreError::UnknownEventId(txn_id.to_string()))?;
2087 let event = get_event(&tx, &existing_event_id)?.ok_or_else(|| MatrixStoreError::UnknownEventId(existing_event_id.clone()))?;
2088 tx.commit()?;
2089 return Ok(DedupedWrite::Existing(event));
2090 }
2091 let event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
2092 txn_dedup_record(&tx, sender_user_id, device_id, txn_id, Some(redaction_event_id), now)?;
2093 tx.commit()?;
2094 Ok(DedupedWrite::New(event))
2095}
2096
2097#[derive(Debug, Clone, PartialEq)]
2102pub struct RoomMember {
2103 pub room_id: String,
2104 pub user_id: i64,
2105 pub membership: Membership,
2106 pub power_level: Option<i64>,
2107 pub updated_at: String,
2108}
2109
2110fn room_member_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<RoomMember> {
2111 let membership_raw: String = row.get(2)?;
2112 Ok(RoomMember {
2113 room_id: row.get(0)?,
2114 user_id: row.get(1)?,
2115 membership: decode_enum(2, "membership", &membership_raw, Membership::from_wire_name)?,
2116 power_level: row.get(3)?,
2117 updated_at: row.get(4)?,
2118 })
2119}
2120
2121const ROOM_MEMBER_SELECT_COLUMNS: &str = "room_id, user_id, membership, power_level, updated_at";
2122
2123pub fn room_members(conn: &Connection, room_id: &str, membership: Option<Membership>) -> rusqlite::Result<Vec<RoomMember>> {
2125 match membership {
2126 Some(m) => {
2127 let mut stmt = conn.prepare(&format!(
2128 "SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1 AND membership = ?2"
2129 ))?;
2130 let rows = stmt.query_map(params![room_id, m.as_str()], room_member_from_row)?;
2131 rows.collect()
2132 }
2133 None => {
2134 let mut stmt = conn.prepare(&format!("SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1"))?;
2135 let rows = stmt.query_map(params![room_id], room_member_from_row)?;
2136 rows.collect()
2137 }
2138 }
2139}
2140
2141pub fn room_member(conn: &Connection, room_id: &str, user_id: i64) -> rusqlite::Result<Option<RoomMember>> {
2146 conn.query_row(
2147 &format!("SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1 AND user_id = ?2"),
2148 params![room_id, user_id],
2149 room_member_from_row,
2150 )
2151 .optional()
2152}
2153
2154pub fn forget_membership(conn: &Connection, room_id: &str, user_id: i64) -> rusqlite::Result<usize> {
2161 conn.execute(
2162 "DELETE FROM room_members WHERE room_id = ?1 AND user_id = ?2 AND membership = 'leave'",
2163 params![room_id, user_id],
2164 )
2165}
2166
2167pub fn room_heroes(conn: &Connection, room_id: &str, exclude_user_id: i64, limit: i64) -> rusqlite::Result<Vec<i64>> {
2171 let mut stmt = conn.prepare(
2172 "SELECT user_id FROM room_members
2173 WHERE room_id = ?1 AND user_id != ?2 AND membership IN ('join', 'invite')
2174 ORDER BY updated_at ASC LIMIT ?3",
2175 )?;
2176 let rows = stmt.query_map(params![room_id, exclude_user_id, limit], |row| row.get(0))?;
2177 rows.collect()
2178}
2179
2180pub fn rooms_for_user(conn: &Connection, user_id: i64, membership: Option<Membership>) -> rusqlite::Result<Vec<String>> {
2184 match membership {
2185 Some(m) => {
2186 let mut stmt = conn.prepare("SELECT room_id FROM room_members WHERE user_id = ?1 AND membership = ?2")?;
2187 let rows = stmt.query_map(params![user_id, m.as_str()], |row| row.get(0))?;
2188 rows.collect()
2189 }
2190 None => {
2191 let mut stmt = conn.prepare("SELECT room_id FROM room_members WHERE user_id = ?1")?;
2192 let rows = stmt.query_map(params![user_id], |row| row.get(0))?;
2193 rows.collect()
2194 }
2195 }
2196}
2197
2198pub fn rooms_changed_in_window(
2213 conn: &Connection,
2214 room_ids: &[String],
2215 caller_user_id: i64,
2216 since_exclusive: i64,
2217 upto_inclusive: i64,
2218) -> rusqlite::Result<HashSet<String>> {
2219 let mut changed = HashSet::new();
2220 if room_ids.is_empty() {
2221 return Ok(changed);
2222 }
2223 let placeholders = vec!["?"; room_ids.len()].join(",");
2224
2225 for table in ["events", "receipts", "pub_events"] {
2229 let sql = format!("SELECT DISTINCT room_id FROM {table} WHERE stream_id > ? AND stream_id <= ? AND room_id IN ({placeholders})");
2230 let mut stmt = conn.prepare(&sql)?;
2231 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&since_exclusive, &upto_inclusive];
2232 for room_id in room_ids {
2233 bound.push(room_id as &dyn rusqlite::ToSql);
2234 }
2235 let mut rows = stmt.query(bound.as_slice())?;
2236 while let Some(row) = rows.next()? {
2237 changed.insert(row.get::<_, String>(0)?);
2238 }
2239 }
2240
2241 let room_id_set: HashSet<&str> = room_ids.iter().map(String::as_str).collect();
2247 let mut stmt = conn.prepare("SELECT DISTINCT room_id FROM account_data WHERE user_id = ?1 AND stream_id > ?2 AND stream_id <= ?3")?;
2248 let mut rows = stmt.query(params![caller_user_id, since_exclusive, upto_inclusive])?;
2249 while let Some(row) = rows.next()? {
2250 let room_id: String = row.get(0)?;
2251 if room_id_set.contains(room_id.as_str()) {
2252 changed.insert(room_id);
2253 }
2254 }
2255
2256 Ok(changed)
2257}
2258
2259pub fn membership_at(conn: &Connection, room_id: &str, mxid: &str, at_stream_id: i64) -> rusqlite::Result<Option<Membership>> {
2266 let content: Option<String> = conn
2267 .query_row(
2268 "SELECT content FROM events
2269 WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key = ?2 AND stream_id <= ?3
2270 ORDER BY stream_id DESC LIMIT 1",
2271 params![room_id, mxid, at_stream_id],
2272 |row| row.get(0),
2273 )
2274 .optional()?;
2275 Ok(content
2276 .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
2277 .and_then(|value| value.get("membership").and_then(|m| m.as_str()).and_then(Membership::from_wire_name)))
2278}
2279
2280pub fn rooms_with_member_events_in_window(conn: &Connection, room_ids: &[String], from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<Vec<String>> {
2287 if room_ids.is_empty() {
2288 return Ok(Vec::new());
2289 }
2290 let placeholders = vec!["?"; room_ids.len()].join(",");
2291 let sql = format!(
2292 "SELECT DISTINCT room_id FROM events
2293 WHERE event_type = 'm.room.member' AND stream_id > ? AND stream_id <= ? AND room_id IN ({placeholders})"
2294 );
2295 let mut stmt = conn.prepare(&sql)?;
2296 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&from_exclusive, &to_inclusive];
2297 for room_id in room_ids {
2298 bound.push(room_id as &dyn rusqlite::ToSql);
2299 }
2300 let rows = stmt.query_map(bound.as_slice(), |row| row.get(0))?;
2301 rows.collect()
2302}
2303
2304pub fn member_state_keys_in_window(conn: &Connection, room_id: &str, from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<Vec<String>> {
2309 let mut stmt = conn.prepare(
2310 "SELECT DISTINCT state_key FROM events
2311 WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key IS NOT NULL AND stream_id > ?2 AND stream_id <= ?3",
2312 )?;
2313 let rows = stmt.query_map(params![room_id, from_exclusive, to_inclusive], |row| row.get(0))?;
2314 rows.collect()
2315}
2316
2317pub fn user_ids_with_leave_transition_in_rooms(
2329 conn: &Connection,
2330 room_ids: &[String],
2331 from_exclusive: i64,
2332 to_inclusive: i64,
2333) -> rusqlite::Result<Vec<i64>> {
2334 if room_ids.is_empty() {
2335 return Ok(Vec::new());
2336 }
2337 let placeholders = vec!["?"; room_ids.len()].join(",");
2338 let sql = format!(
2339 "SELECT DISTINCT e.state_key, e.content FROM events e
2340 WHERE e.event_type = 'm.room.member' AND e.state_key IS NOT NULL
2341 AND e.stream_id > ? AND e.stream_id <= ?
2342 AND e.room_id IN ({placeholders})"
2343 );
2344 let mut stmt = conn.prepare(&sql)?;
2345 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&from_exclusive, &to_inclusive];
2346 for room_id in room_ids {
2347 bound.push(room_id as &dyn rusqlite::ToSql);
2348 }
2349 let mut rows = stmt.query(bound.as_slice())?;
2350 let mut mxids = Vec::new();
2351 while let Some(row) = rows.next()? {
2352 let mxid: String = row.get(0)?;
2353 let content: String = row.get(1)?;
2354 if let Ok(value) = serde_json::from_str::<serde_json::Value>(&content) {
2355 if matches!(value.get("membership").and_then(|m| m.as_str()), Some("leave") | Some("ban")) {
2356 mxids.push(mxid);
2357 }
2358 }
2359 }
2360 let mut user_ids = Vec::new();
2361 for mxid in mxids {
2362 if let Some(user_id) = user_id_of(conn, &mxid)? {
2363 user_ids.push(user_id);
2364 }
2365 }
2366 Ok(user_ids)
2367}
2368
2369pub fn private_room_messages_sent_since(conn: &Connection, sender_user_id: i64, since_ms: i64) -> rusqlite::Result<i64> {
2380 conn.query_row(
2381 "SELECT COUNT(*) FROM events e JOIN rooms r ON r.id = e.room_id
2382 WHERE e.sender_user_id = ?1 AND e.state_key IS NULL AND e.event_type != 'm.room.redaction'
2383 AND e.origin_server_ts >= ?2 AND r.join_rule = 'invite'",
2384 params![sender_user_id, since_ms],
2385 |row| row.get(0),
2386 )
2387}
2388
2389pub fn txn_dedup_count_since(conn: &Connection, user_id: i64, device_id: &str, since: &str) -> rusqlite::Result<i64> {
2400 conn.query_row(
2401 "SELECT COUNT(*) FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND created_at >= ?3",
2402 params![user_id, device_id, since],
2403 |row| row.get(0),
2404 )
2405}
2406
2407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2417pub enum HistoryWindow {
2418 Nothing,
2420 All,
2422 UpTo(i64),
2424}
2425
2426impl HistoryWindow {
2427 pub fn contains(self, stream_id: i64) -> bool {
2428 match self {
2429 HistoryWindow::Nothing => false,
2430 HistoryWindow::All => true,
2431 HistoryWindow::UpTo(upto) => stream_id <= upto,
2432 }
2433 }
2434}
2435
2436pub fn visible_upper_bound(conn: &Connection, room: &Room, caller_user_id: i64) -> Result<HistoryWindow, MatrixStoreError> {
2456 if room.history_visibility == HistoryVisibility::WorldReadable {
2457 return Ok(HistoryWindow::All);
2458 }
2459 let Some(mxid) = mxid_of(conn, caller_user_id)? else {
2460 return Ok(HistoryWindow::Nothing);
2461 };
2462 let Some(member_event) = current_state_event(conn, &room.id, "m.room.member", &mxid)? else {
2463 return Ok(HistoryWindow::Nothing);
2464 };
2465 let content: serde_json::Value = serde_json::from_str(&member_event.content)?;
2466 match content.get("membership").and_then(|v| v.as_str()) {
2467 Some("join") => Ok(HistoryWindow::All),
2468 Some("leave") | Some("ban") => Ok(HistoryWindow::UpTo(member_event.stream_id)),
2469 _ => Ok(HistoryWindow::Nothing),
2470 }
2471}
2472
2473#[derive(Debug, Clone, PartialEq)]
2478pub struct ReceiptRow {
2479 pub room_id: String,
2480 pub user_id: i64,
2481 pub receipt_type: ReceiptType,
2482 pub event_id: String,
2483 pub ts: i64,
2484 pub stream_id: i64,
2485}
2486
2487fn receipt_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<ReceiptRow> {
2488 let receipt_type_raw: String = row.get(2)?;
2489 Ok(ReceiptRow {
2490 room_id: row.get(0)?,
2491 user_id: row.get(1)?,
2492 receipt_type: decode_enum(2, "receipt_type", &receipt_type_raw, ReceiptType::from_wire_name)?,
2493 event_id: row.get(3)?,
2494 ts: row.get(4)?,
2495 stream_id: row.get(5)?,
2496 })
2497}
2498
2499const RECEIPT_SELECT_COLUMNS: &str = "room_id, user_id, receipt_type, event_id, ts, stream_id";
2500
2501pub fn get_receipt(conn: &Connection, room_id: &str, user_id: i64, receipt_type: ReceiptType) -> rusqlite::Result<Option<ReceiptRow>> {
2502 conn.query_row(
2503 &format!("SELECT {RECEIPT_SELECT_COLUMNS} FROM receipts WHERE room_id = ?1 AND user_id = ?2 AND receipt_type = ?3"),
2504 params![room_id, user_id, receipt_type.as_str()],
2505 receipt_from_row,
2506 )
2507 .optional()
2508}
2509
2510pub fn receipts_changed_in_room(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<ReceiptRow>> {
2516 let mut stmt = conn.prepare(&format!(
2517 "SELECT {RECEIPT_SELECT_COLUMNS} FROM receipts WHERE room_id = ?1 AND stream_id > ?2 AND stream_id <= ?3"
2518 ))?;
2519 let rows = stmt.query_map(params![room_id, since_exclusive, upto_inclusive], receipt_from_row)?;
2520 rows.collect()
2521}
2522
2523pub fn upsert_receipt(
2535 conn: &mut Connection,
2536 room_id: &str,
2537 user_id: i64,
2538 receipt_type: ReceiptType,
2539 event_id: &str,
2540 ts: i64,
2541) -> Result<i64, MatrixStoreError> {
2542 let tx = conn.transaction()?;
2543 let stream_id = upsert_receipt_in_tx(&tx, room_id, user_id, receipt_type, event_id, ts)?;
2544 tx.commit()?;
2545 Ok(stream_id)
2546}
2547
2548fn upsert_receipt_in_tx(
2557 tx: &Transaction,
2558 room_id: &str,
2559 user_id: i64,
2560 receipt_type: ReceiptType,
2561 event_id: &str,
2562 ts: i64,
2563) -> Result<i64, MatrixStoreError> {
2564 let target: Option<(i64, String)> = tx
2565 .query_row("SELECT stream_id, room_id FROM events WHERE event_id = ?1", params![event_id], |row| {
2566 Ok((row.get(0)?, row.get(1)?))
2567 })
2568 .optional()?;
2569 let Some((target_position, target_room)) = target else {
2570 if let Some(public) = crate::public_channels::get_event(tx, event_id)? {
2573 if public.room_id != room_id {
2574 return Err(MatrixStoreError::WrongRoom(event_id.to_string()));
2575 }
2576 return Ok(public.stream_id);
2577 }
2578 return Err(MatrixStoreError::UnknownEventId(event_id.to_string()));
2579 };
2580 if target_room != room_id {
2581 return Err(MatrixStoreError::WrongRoom(event_id.to_string()));
2582 }
2583
2584 let existing: Option<(String, i64)> = tx
2585 .query_row(
2586 "SELECT event_id, stream_id FROM receipts WHERE room_id = ?1 AND user_id = ?2 AND receipt_type = ?3",
2587 params![room_id, user_id, receipt_type.as_str()],
2588 |row| Ok((row.get(0)?, row.get(1)?)),
2589 )
2590 .optional()?;
2591
2592 if let Some((existing_event_id, existing_stream_id)) = &existing {
2593 let existing_position: i64 =
2594 tx.query_row("SELECT stream_id FROM events WHERE event_id = ?1", params![existing_event_id], |row| row.get(0))?;
2595 if target_position <= existing_position {
2596 return Ok(*existing_stream_id);
2597 }
2598 }
2599
2600 let stream_id = next_stream_id(tx)?;
2601 tx.execute(
2602 "INSERT INTO receipts (room_id, user_id, receipt_type, event_id, ts, stream_id)
2603 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
2604 ON CONFLICT(room_id, user_id, receipt_type) DO UPDATE SET
2605 event_id = excluded.event_id, ts = excluded.ts, stream_id = excluded.stream_id",
2606 params![room_id, user_id, receipt_type.as_str(), event_id, ts, stream_id],
2607 )?;
2608 Ok(stream_id)
2609}
2610
2611const NOTIFICATION_MESSAGE_TYPES_SQL: &str = "('m.room.message', 'm.room.encrypted', 'org.example.legacy_dm')";
2623
2624pub fn notification_count(conn: &Connection, room: &Room, user_id: i64) -> Result<i64, MatrixStoreError> {
2643 if room.kind == RoomKind::Channel && !room.is_encrypted {
2644 return Ok(0); }
2646 let upper_bound = match visible_upper_bound(conn, room, user_id)? {
2647 HistoryWindow::Nothing => return Ok(0),
2648 HistoryWindow::All => i64::MAX,
2649 HistoryWindow::UpTo(upper) => upper,
2650 };
2651
2652 let mut after_stream_id: i64 = 0;
2653 for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
2654 if let Some(receipt) = get_receipt(conn, &room.id, user_id, receipt_type)? {
2655 if let Some(target) = get_event(conn, &receipt.event_id)? {
2656 after_stream_id = after_stream_id.max(target.stream_id);
2657 }
2658 }
2659 }
2660
2661 let count = conn.query_row(
2662 &format!(
2663 "SELECT COUNT(*) FROM events
2664 WHERE room_id = ?1 AND state_key IS NULL AND sender_user_id != ?2
2665 AND stream_id > ?3 AND stream_id <= ?4
2666 AND event_type IN {NOTIFICATION_MESSAGE_TYPES_SQL}"
2667 ),
2668 params![room.id, user_id, after_stream_id, upper_bound],
2669 |row| row.get(0),
2670 )?;
2671 Ok(count)
2672}
2673
2674pub const GLOBAL_ACCOUNT_DATA_ROOM: &str = "";
2681
2682#[derive(Debug, Clone, PartialEq)]
2683pub struct AccountDataRow {
2684 pub user_id: i64,
2685 pub room_id: String,
2686 pub data_type: String,
2687 pub content: String,
2688 pub stream_id: i64,
2689}
2690
2691fn account_data_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<AccountDataRow> {
2692 Ok(AccountDataRow {
2693 user_id: row.get(0)?,
2694 room_id: row.get(1)?,
2695 data_type: row.get(2)?,
2696 content: row.get(3)?,
2697 stream_id: row.get(4)?,
2698 })
2699}
2700
2701const ACCOUNT_DATA_SELECT_COLUMNS: &str = "user_id, room_id, data_type, content, stream_id";
2702
2703pub fn upsert_account_data(conn: &mut Connection, user_id: i64, room_id: &str, data_type: &str, content: &str) -> Result<i64, MatrixStoreError> {
2707 let tx = conn.transaction()?;
2708 let stream_id = next_stream_id(&tx)?;
2709 tx.execute(
2710 "INSERT INTO account_data (user_id, room_id, data_type, content, stream_id) VALUES (?1, ?2, ?3, ?4, ?5)
2711 ON CONFLICT(user_id, room_id, data_type) DO UPDATE SET content = excluded.content, stream_id = excluded.stream_id",
2712 params![user_id, room_id, data_type, content, stream_id],
2713 )?;
2714 tx.commit()?;
2715 Ok(stream_id)
2716}
2717
2718pub fn get_account_data(conn: &Connection, user_id: i64, room_id: &str, data_type: &str) -> rusqlite::Result<Option<AccountDataRow>> {
2719 conn.query_row(
2720 &format!("SELECT {ACCOUNT_DATA_SELECT_COLUMNS} FROM account_data WHERE user_id = ?1 AND room_id = ?2 AND data_type = ?3"),
2721 params![user_id, room_id, data_type],
2722 account_data_from_row,
2723 )
2724 .optional()
2725}
2726
2727pub fn account_data_since(conn: &Connection, user_id: i64, room_id: &str, since_stream: i64) -> rusqlite::Result<Vec<AccountDataRow>> {
2732 let mut stmt = conn.prepare(&format!(
2733 "SELECT {ACCOUNT_DATA_SELECT_COLUMNS} FROM account_data WHERE user_id = ?1 AND room_id = ?2 AND stream_id > ?3 ORDER BY stream_id ASC"
2734 ))?;
2735 let rows = stmt.query_map(params![user_id, room_id, since_stream], account_data_from_row)?;
2736 rows.collect()
2737}
2738
2739#[derive(Debug, Clone, PartialEq, Eq)]
2745pub enum TxnDedupEntry {
2746 NotSeen,
2749 Seen(Option<String>),
2752}
2753
2754pub fn txn_dedup_lookup(conn: &Connection, user_id: i64, device_id: &str, txn_id: &str) -> rusqlite::Result<TxnDedupEntry> {
2757 let found: Option<Option<String>> = conn
2758 .query_row(
2759 "SELECT event_id FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND txn_id = ?3",
2760 params![user_id, device_id, txn_id],
2761 |row| row.get(0),
2762 )
2763 .optional()?;
2764 Ok(match found {
2765 None => TxnDedupEntry::NotSeen,
2766 Some(event_id) => TxnDedupEntry::Seen(event_id),
2767 })
2768}
2769
2770pub fn txn_dedup_record(conn: &Connection, user_id: i64, device_id: &str, txn_id: &str, event_id: Option<&str>, now: &str) -> rusqlite::Result<()> {
2775 conn.execute(
2776 "INSERT INTO txn_dedup (user_id, device_id, txn_id, event_id, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
2777 params![user_id, device_id, txn_id, event_id, now],
2778 )?;
2779 Ok(())
2780}
2781
2782pub fn txn_id_for_event(conn: &Connection, user_id: i64, device_id: &str, event_id: &str) -> rusqlite::Result<Option<String>> {
2790 conn.query_row(
2791 "SELECT txn_id FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND event_id = ?3",
2792 params![user_id, device_id, event_id],
2793 |row| row.get(0),
2794 )
2795 .optional()
2796}
2797
2798pub fn create_filter(conn: &Connection, user_id: i64, definition: &str) -> rusqlite::Result<i64> {
2804 conn.execute("INSERT INTO filters (user_id, definition) VALUES (?1, ?2)", params![user_id, definition])?;
2805 Ok(conn.last_insert_rowid())
2806}
2807
2808pub fn get_filter(conn: &Connection, user_id: i64, filter_id: i64) -> rusqlite::Result<Option<String>> {
2811 conn.query_row(
2812 "SELECT definition FROM filters WHERE id = ?1 AND user_id = ?2",
2813 params![filter_id, user_id],
2814 |row| row.get(0),
2815 )
2816 .optional()
2817}
2818
2819pub fn insert_legacy_dm_message_map(conn: &Connection, legacy_message_id: i64, event_id: &str) -> rusqlite::Result<()> {
2824 conn.execute(
2825 "INSERT INTO legacy_dm_message_map (legacy_message_id, event_id) VALUES (?1, ?2)",
2826 params![legacy_message_id, event_id],
2827 )?;
2828 Ok(())
2829}
2830
2831pub fn legacy_dm_message_event_id(conn: &Connection, legacy_message_id: i64) -> rusqlite::Result<Option<String>> {
2832 conn.query_row(
2833 "SELECT event_id FROM legacy_dm_message_map WHERE legacy_message_id = ?1",
2834 params![legacy_message_id],
2835 |row| row.get(0),
2836 )
2837 .optional()
2838}
2839
2840pub fn highest_mapped_legacy_message_id(conn: &Connection, room_id: &str) -> rusqlite::Result<Option<i64>> {
2853 conn.query_row(
2854 "SELECT MAX(m.legacy_message_id) FROM legacy_dm_message_map m
2855 JOIN events e ON e.event_id = m.event_id
2856 WHERE e.room_id = ?1",
2857 params![room_id],
2858 |row| row.get(0),
2859 )
2860}
2861
2862#[derive(Debug, Clone, PartialEq)]
2898pub struct LegacyDmMessageImport {
2899 pub legacy_message_id: i64,
2900 pub event_id: String,
2901 pub sender_user_id: i64,
2902 pub content: String,
2903 pub origin_server_ts: i64,
2904}
2905
2906#[derive(Debug, Clone, PartialEq)]
2912pub struct LegacyDmReadReceipt {
2913 pub reader_user_id: i64,
2914 pub up_to_legacy_message_id: i64,
2915 pub ts_ms: i64,
2916}
2917
2918#[derive(Debug, Clone, PartialEq)]
2923pub struct LegacyDmDirectHint {
2924 pub user_id: i64,
2925 pub peer_mxid: String,
2926}
2927
2928#[derive(Debug, Clone, Copy)]
2933pub struct DmMigrationExtras<'a> {
2934 pub messages: &'a [LegacyDmMessageImport],
2935 pub receipts: &'a [LegacyDmReadReceipt],
2936 pub direct_hints: &'a [LegacyDmDirectHint],
2937}
2938
2939#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2941pub struct DmMigrationCounts {
2942 pub messages_imported: usize,
2943 pub receipts_set: usize,
2944}
2945
2946fn merge_m_direct_in_tx(tx: &Transaction, user_id: i64, peer_mxid: &str, room_id: &str) -> Result<(), MatrixStoreError> {
2951 let existing: Option<String> = tx
2952 .query_row(
2953 "SELECT content FROM account_data WHERE user_id = ?1 AND room_id = ?2 AND data_type = 'm.direct'",
2954 params![user_id, GLOBAL_ACCOUNT_DATA_ROOM],
2955 |row| row.get(0),
2956 )
2957 .optional()?;
2958
2959 let mut direct: serde_json::Map<String, serde_json::Value> = match existing {
2960 Some(content) => serde_json::from_str(&content)?,
2961 None => serde_json::Map::new(),
2962 };
2963 let rooms_entry = direct.entry(peer_mxid.to_string()).or_insert_with(|| serde_json::Value::Array(Vec::new()));
2964 if !rooms_entry.is_array() {
2965 *rooms_entry = serde_json::Value::Array(Vec::new());
2966 }
2967 if let serde_json::Value::Array(list) = rooms_entry {
2968 if !list.iter().any(|v| v.as_str() == Some(room_id)) {
2969 list.push(serde_json::Value::String(room_id.to_string()));
2970 }
2971 }
2972
2973 let stream_id = next_stream_id(tx)?;
2974 tx.execute(
2975 "INSERT INTO account_data (user_id, room_id, data_type, content, stream_id) VALUES (?1, ?2, 'm.direct', ?3, ?4)
2976 ON CONFLICT(user_id, room_id, data_type) DO UPDATE SET content = excluded.content, stream_id = excluded.stream_id",
2977 params![user_id, GLOBAL_ACCOUNT_DATA_ROOM, serde_json::Value::Object(direct).to_string(), stream_id],
2978 )?;
2979 Ok(())
2980}
2981
2982pub fn migrate_dm_conversation(
2997 conn: &mut Connection,
2998 bootstrap: RoomBootstrap<'_>,
2999 state_events: &[NewStateEvent],
3000 bootstrap_origin_server_ts: i64,
3001 extras: DmMigrationExtras<'_>,
3002) -> Result<(Room, DmMigrationCounts), MatrixStoreError> {
3003 let tx = conn.transaction()?;
3004
3005 insert_room_row(
3006 &tx,
3007 bootstrap.room_id,
3008 bootstrap.kind,
3009 bootstrap.creator_user_id,
3010 bootstrap.created_at,
3011 bootstrap.is_encrypted,
3012 bootstrap.join_rule,
3013 bootstrap.history_visibility,
3014 bootstrap.dm_pair_key,
3015 bootstrap.legacy_dm_id,
3016 )?;
3017 for event in state_events {
3018 apply_state_event_in_tx(
3019 &tx,
3020 &event.event_id,
3021 bootstrap.room_id,
3022 event.sender_user_id,
3023 &event.event_type,
3024 &event.state_key,
3025 &event.content,
3026 bootstrap_origin_server_ts,
3027 bootstrap.created_at,
3028 )?;
3029 }
3030
3031 let mut event_id_by_legacy_id: std::collections::HashMap<i64, String> = std::collections::HashMap::new();
3032 for message in extras.messages {
3033 insert_timeline_event_in_tx(
3034 &tx,
3035 &TimelineEventRow {
3036 event_id: &message.event_id,
3037 room_id: bootstrap.room_id,
3038 sender_user_id: message.sender_user_id,
3039 event_type: "org.example.legacy_dm",
3040 content: &message.content,
3041 origin_server_ts: message.origin_server_ts,
3042 txn_id: None,
3043 },
3044 )?;
3045 insert_legacy_dm_message_map(&tx, message.legacy_message_id, &message.event_id)?;
3046 event_id_by_legacy_id.insert(message.legacy_message_id, message.event_id.clone());
3047 }
3048
3049 let mut receipts_set = 0usize;
3050 for receipt in extras.receipts {
3051 let Some(target_event_id) = event_id_by_legacy_id.get(&receipt.up_to_legacy_message_id) else { continue };
3052 let target_stream_id: i64 =
3053 tx.query_row("SELECT stream_id FROM events WHERE event_id = ?1", params![target_event_id], |row| row.get(0))?;
3054 tx.execute(
3055 "INSERT INTO receipts (room_id, user_id, receipt_type, event_id, ts, stream_id) VALUES (?1, ?2, 'm.read', ?3, ?4, ?5)",
3056 params![bootstrap.room_id, receipt.reader_user_id, target_event_id, receipt.ts_ms, target_stream_id],
3057 )?;
3058 receipts_set += 1;
3059 }
3060
3061 for hint in extras.direct_hints {
3062 merge_m_direct_in_tx(&tx, hint.user_id, &hint.peer_mxid, bootstrap.room_id)?;
3063 }
3064
3065 tx.commit()?;
3066
3067 Ok((
3068 Room {
3069 id: bootstrap.room_id.to_string(),
3070 kind: bootstrap.kind,
3071 room_version: MATRIX_ROOM_VERSION.to_string(),
3072 creator_user_id: bootstrap.creator_user_id,
3073 created_at: bootstrap.created_at.to_string(),
3074 is_encrypted: bootstrap.is_encrypted,
3075 join_rule: bootstrap.join_rule,
3076 history_visibility: bootstrap.history_visibility,
3077 dm_pair_key: bootstrap.dm_pair_key.map(str::to_string),
3078 legacy_dm_id: bootstrap.legacy_dm_id,
3079 },
3080 DmMigrationCounts { messages_imported: extras.messages.len(), receipts_set },
3081 ))
3082}
3083
3084pub fn catch_up_dm_conversation(
3112 conn: &mut Connection,
3113 room_id: &str,
3114 messages: &[LegacyDmMessageImport],
3115 receipts: &[LegacyDmReadReceipt],
3116) -> Result<DmMigrationCounts, MatrixStoreError> {
3117 let tx = conn.transaction()?;
3118 let counts = catch_up_dm_in_tx(&tx, room_id, messages, receipts)?;
3119 tx.commit()?;
3120 Ok(counts)
3121}
3122
3123fn catch_up_dm_in_tx(
3128 tx: &Transaction,
3129 room_id: &str,
3130 messages: &[LegacyDmMessageImport],
3131 receipts: &[LegacyDmReadReceipt],
3132) -> Result<DmMigrationCounts, MatrixStoreError> {
3133 for message in messages {
3134 insert_timeline_event_in_tx(
3135 tx,
3136 &TimelineEventRow {
3137 event_id: &message.event_id,
3138 room_id,
3139 sender_user_id: message.sender_user_id,
3140 event_type: "org.example.legacy_dm",
3141 content: &message.content,
3142 origin_server_ts: message.origin_server_ts,
3143 txn_id: None,
3144 },
3145 )?;
3146 insert_legacy_dm_message_map(tx, message.legacy_message_id, &message.event_id)?;
3147 }
3148
3149 let mut receipts_set = 0usize;
3150 for receipt in receipts {
3151 let Some(target_event_id) = legacy_dm_message_event_id(tx, receipt.up_to_legacy_message_id)? else { continue };
3152 upsert_receipt_in_tx(tx, room_id, receipt.reader_user_id, ReceiptType::Read, &target_event_id, receipt.ts_ms)?;
3153 receipts_set += 1;
3154 }
3155
3156 Ok(DmMigrationCounts { messages_imported: messages.len(), receipts_set })
3157}
3158
3159#[derive(Debug, Clone, Copy)]
3163pub struct DmAdoption<'a> {
3164 pub room_id: &'a str,
3166 pub legacy_dm_id: i64,
3168 pub key_events: &'a [NewStateEvent],
3173 pub key_events_origin_server_ts: i64,
3174 pub now: &'a str,
3176}
3177
3178pub fn adopt_dm_room_for_legacy(
3192 conn: &mut Connection,
3193 adoption: DmAdoption<'_>,
3194 messages: &[LegacyDmMessageImport],
3195 receipts: &[LegacyDmReadReceipt],
3196) -> Result<Option<DmMigrationCounts>, MatrixStoreError> {
3197 let tx = conn.transaction()?;
3198
3199 let bound = tx.execute(
3200 "UPDATE rooms SET legacy_dm_id = ?1 WHERE id = ?2 AND kind = 'dm' AND legacy_dm_id IS NULL",
3201 params![adoption.legacy_dm_id, adoption.room_id],
3202 )?;
3203 if bound == 0 {
3204 return Ok(None);
3205 }
3206
3207 for event in adoption.key_events {
3208 if current_state_event(&tx, adoption.room_id, &event.event_type, &event.state_key)?.is_some() {
3209 continue;
3210 }
3211 apply_state_event_in_tx(
3212 &tx,
3213 &event.event_id,
3214 adoption.room_id,
3215 event.sender_user_id,
3216 &event.event_type,
3217 &event.state_key,
3218 &event.content,
3219 adoption.key_events_origin_server_ts,
3220 adoption.now,
3221 )?;
3222 }
3223
3224 let counts = catch_up_dm_in_tx(&tx, adoption.room_id, messages, receipts)?;
3225 tx.commit()?;
3226 Ok(Some(counts))
3227}
3228
3229#[derive(Debug, Clone, PartialEq)]
3241pub struct PublicRoomSummary {
3242 pub room_id: String,
3243 pub name: Option<String>,
3244 pub topic: Option<String>,
3245 pub num_joined_members: i64,
3246 pub world_readable: bool,
3247}
3248
3249pub fn public_rooms_page(
3270 conn: &Connection,
3271 after_room_id: Option<&str>,
3272 limit: usize,
3273 search_term: Option<&str>,
3274) -> Result<(Vec<PublicRoomSummary>, bool, i64), MatrixStoreError> {
3275 let mut stmt = conn.prepare(
3276 "SELECT r.id, r.history_visibility,
3277 (SELECT COUNT(*) FROM room_members WHERE room_id = r.id AND membership = 'join')
3278 FROM rooms r WHERE r.join_rule = 'public' ORDER BY r.id ASC",
3279 )?;
3280 let mut rows = stmt.query([])?;
3281 let mut all = Vec::new();
3282 while let Some(row) = rows.next()? {
3283 let room_id: String = row.get(0)?;
3284 let history_visibility_raw: String = row.get(1)?;
3285 let num_joined_members: i64 = row.get(2)?;
3286 let world_readable = history_visibility_raw == HistoryVisibility::WorldReadable.as_str();
3287
3288 let name = current_state_event(conn, &room_id, "m.room.name", "")?
3289 .and_then(|e| serde_json::from_str::<serde_json::Value>(&e.content).ok())
3290 .and_then(|v| v.get("name").and_then(|n| n.as_str()).map(str::to_string));
3291 let topic = current_state_event(conn, &room_id, "m.room.topic", "")?
3292 .and_then(|e| serde_json::from_str::<serde_json::Value>(&e.content).ok())
3293 .and_then(|v| v.get("topic").and_then(|t| t.as_str()).map(str::to_string));
3294
3295 all.push(PublicRoomSummary { room_id, name, topic, num_joined_members, world_readable });
3296 }
3297 drop(rows);
3298 drop(stmt);
3299
3300 let total_room_count_estimate = all.len() as i64;
3301
3302 let filtered: Vec<PublicRoomSummary> = match search_term {
3303 Some(term) if !term.is_empty() => {
3304 let term_lower = term.to_lowercase();
3305 all.into_iter().filter(|room| room.name.as_deref().is_some_and(|n| n.to_lowercase().contains(&term_lower))).collect()
3306 }
3307 _ => all,
3308 };
3309
3310 let start = match after_room_id {
3311 Some(cursor) => filtered.iter().position(|r| r.room_id == cursor).map_or(0, |idx| idx + 1),
3312 None => 0,
3313 };
3314 let has_more = filtered.len() > start + limit;
3315 let page: Vec<PublicRoomSummary> = filtered.into_iter().skip(start).take(limit).collect();
3316 Ok((page, has_more, total_room_count_estimate))
3317}
3318
3319#[cfg(test)]
3320mod tests {
3321 use super::*;
3322
3323 const T0: &str = "2026-09-24T00:00:00+00:00";
3324 const ROOM: &str = "!testroom:example.org";
3325
3326 fn test_conn() -> Connection {
3327 let conn = Connection::open_in_memory().expect("in-memory sqlite");
3328 create_matrix_schema(&conn).expect("schema");
3329 conn
3330 }
3331
3332 fn ensure_legacy_dm_map_table(conn: &Connection) {
3333 conn.execute_batch(
3334 "CREATE TABLE IF NOT EXISTS legacy_dm_message_map (
3335 legacy_message_id INTEGER PRIMARY KEY,
3336 event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id)
3337 );",
3338 )
3339 .expect("legacy map table for remaining unit tests");
3340 }
3341
3342 fn make_room(conn: &Connection) {
3343 create_room(conn, ROOM, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None).expect("create room");
3344 }
3345
3346 #[test]
3349 fn next_stream_id_is_strictly_monotonic_across_every_table() {
3350 let mut conn = test_conn();
3351 make_room(&conn);
3352
3353 let event = insert_timeline_event(&mut conn, "$event1", ROOM, 1, "m.room.message", "{}", 1000).expect("insert event");
3354 let account_data_stream = upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", "{}").expect("account data");
3355 let receipt_stream = upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, "$event1", 1500).expect("receipt");
3356
3357 assert!(event.stream_id < account_data_stream, "event {} should precede account data {}", event.stream_id, account_data_stream);
3358 assert!(
3359 account_data_stream < receipt_stream,
3360 "account data {account_data_stream} should precede receipt {receipt_stream}"
3361 );
3362 }
3363
3364 #[test]
3367 fn apply_state_event_replaces_current_state_but_keeps_history_in_events() {
3368 let mut conn = test_conn();
3369 make_room(&conn);
3370
3371 apply_state_event(&mut conn, &StateEventWrite { event_id: "$e1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.name", state_key: "", content: r#"{"name":"first"}"#, origin_server_ts: 1000, now: T0 }).expect("apply first");
3372 apply_state_event(&mut conn, &StateEventWrite { event_id: "$e2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.name", state_key: "", content: r#"{"name":"second"}"#, origin_server_ts: 2000, now: T0 }).expect("apply second");
3373
3374 let current = current_state_event(&conn, ROOM, "m.room.name", "").expect("query").expect("row exists");
3375 assert_eq!(current.event_id, "$e2");
3376 assert_eq!(current.content, r#"{"name":"second"}"#);
3377
3378 let e1 = get_event(&conn, "$e1").expect("get e1").expect("row exists");
3379 let e2 = get_event(&conn, "$e2").expect("get e2").expect("row exists");
3380 assert_eq!(e1.content, r#"{"name":"first"}"#);
3381 assert_eq!(e2.content, r#"{"name":"second"}"#);
3382 }
3383
3384 #[test]
3387 fn redact_event_strips_content_per_v11_allow_list_by_type() {
3388 let cases: Vec<(&str, &str, serde_json::Value)> = vec![
3389 (
3390 "m.room.member",
3391 r#"{"membership":"join","join_authorised_via_users_server":"@x:example.org","avatar_url":"mxc://x","displayname":"Bob","third_party_invite":{"display_name":"bob@example.com","signed":{"mxid":"@bob:example.org"}}}"#,
3392 serde_json::json!({
3393 "membership": "join",
3394 "join_authorised_via_users_server": "@x:example.org",
3395 "third_party_invite": {"signed": {"mxid": "@bob:example.org"}}
3396 }),
3397 ),
3398 (
3399 "m.room.power_levels",
3400 r#"{"ban":50,"events":{},"events_default":0,"invite":50,"kick":50,"redact":50,"state_default":50,"users":{"@a:x":100},"users_default":0,"extra":"drop me"}"#,
3401 serde_json::json!({
3402 "ban": 50, "events": {}, "events_default": 0, "invite": 50, "kick": 50,
3403 "redact": 50, "state_default": 50, "users": {"@a:x": 100}, "users_default": 0
3404 }),
3405 ),
3406 (
3407 "m.room.history_visibility",
3408 r#"{"history_visibility":"shared","extra":"drop"}"#,
3409 serde_json::json!({"history_visibility": "shared"}),
3410 ),
3411 (
3416 "m.room.message",
3417 r#"{"body":"hi","msgtype":"m.text"}"#,
3418 serde_json::json!({}),
3419 ),
3420 ];
3421
3422 for (event_type, content, expected) in cases {
3423 let mut conn = test_conn();
3424 make_room(&conn);
3425 insert_timeline_event(&mut conn, "$target", ROOM, 1, event_type, content, 1000).expect("insert target");
3426 redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).expect("redact");
3427
3428 let target = get_event(&conn, "$target").expect("get target").expect("row exists");
3429 let got: serde_json::Value = serde_json::from_str(&target.content).expect("parse stripped content");
3430 assert_eq!(got, expected, "event_type={event_type}");
3431 assert_eq!(target.redacted_by.as_deref(), Some("$redaction"), "event_type={event_type}");
3432 }
3433 }
3434
3435 #[test]
3436 fn redaction_content_carries_the_target_as_redacts_for_every_write_path() {
3437 let mut conn = test_conn();
3438 make_room(&conn);
3439 insert_timeline_event(&mut conn, "$t1", ROOM, 1, "m.room.message", "{}", 1000).expect("insert t1");
3440 insert_timeline_event(&mut conn, "$t2", ROOM, 1, "m.room.message", "{}", 1100).expect("insert t2");
3441 insert_timeline_event(&mut conn, "$t3", ROOM, 1, "m.room.message", "{}", 1200).expect("insert t3");
3442
3443 let plain = redact_event(&mut conn, ROOM, "$t1", "$r1", 1, None, 2000).expect("plain redact");
3444 assert_eq!(serde_json::from_str::<serde_json::Value>(&plain.content).expect("json"), serde_json::json!({ "redacts": "$t1" }));
3445 assert_eq!(plain.redacts.as_deref(), Some("$t1"));
3446
3447 let marked = redact_event_marked(
3448 &mut conn,
3449 &Redaction { room_id: ROOM, target_event_id: "$t2", redaction_event_id: "$r2", sender_user_id: 1, reason: Some("spam"), origin_server_ts: 2100 },
3450 &serde_json::json!({ "org.example.site_moderation": true }),
3451 )
3452 .expect("marked redact");
3453 assert_eq!(
3454 serde_json::from_str::<serde_json::Value>(&marked.content).expect("json"),
3455 serde_json::json!({ "redacts": "$t2", "reason": "spam", "org.example.site_moderation": true })
3456 );
3457
3458 let deduped = redact_event_deduped(&mut conn, "DEV1", "txn-1", ROOM, "$t3", "$r3", 1, None, 2200, T0).expect("deduped redact");
3459 let DedupedWrite::New(event) = deduped else { panic!("first write is new") };
3460 let stored = get_event(&conn, &event.event_id).expect("get").expect("row exists");
3461 assert_eq!(serde_json::from_str::<serde_json::Value>(&stored.content).expect("json")["redacts"], "$t3");
3462 }
3463
3464 #[test]
3465 fn redact_content_per_v11_keeps_everything_for_m_room_create() {
3466 let content = r#"{"room_version":"11","creator":"@a:example.org"}"#;
3467 let stripped = redact_content_per_v11("m.room.create", content).expect("strip");
3468 let got: serde_json::Value = serde_json::from_str(&stripped).expect("parse");
3469 assert_eq!(got, serde_json::json!({"room_version": "11", "creator": "@a:example.org"}));
3470 }
3471
3472 #[test]
3475 fn txn_dedup_returns_the_same_event_id_on_a_repeated_txn_id() {
3476 let mut conn = test_conn();
3477 make_room(&conn);
3478
3479 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-1").expect("lookup 1"), TxnDedupEntry::NotSeen);
3480
3481 let event = {
3482 let tx = conn.transaction().expect("tx");
3483 let row = TimelineEventRow {
3484 event_id: "$e1",
3485 room_id: ROOM,
3486 sender_user_id: 1,
3487 event_type: "m.room.message",
3488 content: "{}",
3489 origin_server_ts: 1000,
3490 txn_id: Some("txn-1"),
3491 };
3492 let event = insert_timeline_event_in_tx(&tx, &row).expect("insert");
3493 tx.commit().expect("commit");
3494 event
3495 };
3496 txn_dedup_record(&conn, 1, "DEV1", "txn-1", Some(&event.event_id), T0).expect("record");
3497
3498 assert_eq!(
3499 txn_dedup_lookup(&conn, 1, "DEV1", "txn-1").expect("lookup 2"),
3500 TxnDedupEntry::Seen(Some(event.event_id.clone()))
3501 );
3502
3503 let count: i64 = conn.query_row("SELECT COUNT(*) FROM events", [], |row| row.get(0)).expect("count");
3504 assert_eq!(count, 1, "a repeated txn_id must never create a second events row");
3505 }
3506
3507 #[test]
3510 fn relations_index_populated_from_cleartext_relates_to_even_when_content_is_ciphertext() {
3511 let mut conn = test_conn();
3512 make_room(&conn);
3513 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3514
3515 let content = r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"opaque-base64==","sender_key":"opaque","m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3516 let reaction = insert_timeline_event(&mut conn, "$reaction", ROOM, 2, "m.room.encrypted", content, 2000).expect("insert reaction");
3517
3518 let (rel_type, target_id, agg_key): (String, String, Option<String>) = conn
3519 .query_row(
3520 "SELECT rel_type, target_id, agg_key FROM relations WHERE event_id = ?1",
3521 params![reaction.event_id],
3522 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
3523 )
3524 .expect("relation row exists");
3525 assert_eq!(rel_type, "m.annotation");
3526 assert_eq!(target_id, "$target");
3527 assert_eq!(agg_key.as_deref(), Some("a"));
3528 }
3529
3530 #[test]
3533 fn account_data_global_row_is_unique() {
3534 let mut conn = test_conn();
3535 upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", r#"{"v":1}"#).expect("first upsert");
3536 upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", r#"{"v":2}"#).expect("second upsert");
3537
3538 let count: i64 = conn
3539 .query_row(
3540 "SELECT COUNT(*) FROM account_data WHERE user_id = 1 AND room_id = ''",
3541 [],
3542 |row| row.get(0),
3543 )
3544 .expect("count");
3545 assert_eq!(count, 1, "two global upserts of the same type must leave one row");
3546
3547 let row = get_account_data(&conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct").expect("get").expect("row exists");
3548 assert_eq!(row.content, r#"{"v":2}"#);
3549 }
3550
3551 #[test]
3554 fn receipt_never_moves_backwards() {
3555 let mut conn = test_conn();
3556 make_room(&conn);
3557 let e1 = insert_timeline_event(&mut conn, "$e1", ROOM, 1, "m.room.message", "{}", 1000).expect("e1");
3558 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 1, "m.room.message", "{}", 2000).expect("e2");
3559
3560 upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &e2.event_id, 5000).expect("advance to e2");
3561 upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &e1.event_id, 6000).expect("attempted backward move is a no-op");
3562
3563 let receipt = get_receipt(&conn, ROOM, 9, ReceiptType::Read).expect("get").expect("row exists");
3564 assert_eq!(receipt.event_id, e2.event_id, "a receipt must never move back to an earlier event");
3565 }
3566
3567 #[test]
3570 fn receipt_further_ahead_of_the_two_types_wins_for_notification_count() {
3571 let mut conn = test_conn();
3572 make_room(&conn);
3573 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3574 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 900, now: T0 }).expect("alice joins");
3575
3576 insert_timeline_event(&mut conn, "$e1", ROOM, 2, "m.room.message", "{}", 1000).expect("e1");
3577 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 2, "m.room.message", "{}", 2000).expect("e2");
3578 insert_timeline_event(&mut conn, "$e3", ROOM, 2, "m.room.message", "{}", 3000).expect("e3");
3579 let e4 = insert_timeline_event(&mut conn, "$e4", ROOM, 2, "m.room.message", "{}", 4000).expect("e4");
3580 let e5 = insert_timeline_event(&mut conn, "$e5", ROOM, 2, "m.room.message", "{}", 5000).expect("e5");
3581
3582 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, &e2.event_id, 2500).expect("read receipt");
3586 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::ReadPrivate, &e4.event_id, 4500).expect("private receipt further ahead");
3587
3588 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3589 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 1, "only $e5 is after the further-ahead receipt ($e4)");
3590
3591 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, &e5.event_id, 5500).expect("read receipt advances past e5");
3594 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 0, "m.read now covers every message");
3595 }
3596
3597 #[test]
3598 fn notification_count_ignores_own_state_and_reactions() {
3599 let mut conn = test_conn();
3600 make_room(&conn);
3601 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3602 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 900, now: T0 }).expect("alice joins");
3603
3604 insert_timeline_event(&mut conn, "$bob-msg", ROOM, 2, "m.room.message", "{}", 1000).expect("bob message");
3606 insert_timeline_event(&mut conn, "$bob-enc", ROOM, 2, "m.room.encrypted", "{}", 1100).expect("bob encrypted");
3607 insert_timeline_event(&mut conn, "$bob-legacy", ROOM, 2, "org.example.legacy_dm", "{}", 1200).expect("bob legacy dm");
3608
3609 insert_timeline_event(&mut conn, "$alice-msg", ROOM, 1, "m.room.message", "{}", 1300).expect("alice's own message");
3611 apply_state_event(&mut conn, &StateEventWrite { event_id: "$name", room_id: ROOM, sender_user_id: 2, event_type: "m.room.name", state_key: "", content: r#"{"name":"x"}"#, origin_server_ts: 1400, now: T0 }).expect("state event");
3612 insert_timeline_event(&mut conn, "$reaction", ROOM, 2, "m.reaction", "{}", 1500).expect("reaction");
3613
3614 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3615 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 3, "only bob's 3 message-like sends count");
3616 }
3617
3618 #[test]
3619 fn notification_count_is_zero_for_a_non_member() {
3620 let mut conn = test_conn();
3621 make_room(&conn);
3622 insert_timeline_event(&mut conn, "$e1", ROOM, 2, "m.room.message", "{}", 1000).expect("e1");
3623
3624 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3625 assert_eq!(notification_count(&conn, &room, 999).expect("count"), 0, "a caller with no membership row at all sees nothing");
3626 }
3627
3628 #[test]
3631 fn duplicate_annotation_is_refused() {
3632 let mut conn = test_conn();
3633 make_room(&conn);
3634 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3635
3636 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3637 insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).expect("first reaction");
3638
3639 let err = insert_timeline_event(&mut conn, "$react2", ROOM, 5, "m.reaction", content, 3000).unwrap_err();
3640 assert!(matches!(err, MatrixStoreError::DuplicateAnnotation));
3641
3642 assert_eq!(get_event(&conn, "$react2").expect("get"), None);
3644
3645 let other = insert_timeline_event(&mut conn, "$react3", ROOM, 6, "m.reaction", content, 4000).expect("different sender reacts");
3648 assert_eq!(other.event_id, "$react3");
3649 }
3650
3651 #[test]
3654 fn reannotation_after_redaction_is_allowed() {
3655 let mut conn = test_conn();
3656 make_room(&conn);
3657 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3658
3659 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3660 let first = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).expect("first reaction");
3661
3662 redact_event(&mut conn, ROOM, &first.event_id, "$redaction", 1, None, 2500).expect("redact the reaction");
3663
3664 let second = insert_timeline_event(&mut conn, "$react2", ROOM, 5, "m.reaction", content, 3000)
3667 .expect("re-annotation after redaction must succeed");
3668 assert_eq!(second.event_id, "$react2");
3669 }
3670
3671 #[test]
3672 fn relation_target_must_exist() {
3673 let mut conn = test_conn();
3674 make_room(&conn);
3675
3676 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$missing","key":"a"}}"#;
3677 let err = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).unwrap_err();
3678 assert!(matches!(err, MatrixStoreError::InvalidRelationTarget(ref id) if id == "$missing"));
3679 }
3680
3681 #[test]
3682 fn relation_target_must_be_in_the_same_room() {
3683 let mut conn = test_conn();
3684 make_room(&conn);
3685 let other_room = format!("!other:{}", matrix_server_name());
3686 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3687 .expect("other room");
3688 insert_timeline_event(&mut conn, "$target", &other_room, 1, "m.room.message", "{}", 1000).expect("target in other room");
3689
3690 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3691 let err = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).unwrap_err();
3692 assert!(matches!(err, MatrixStoreError::InvalidRelationTarget(ref id) if id == "$target"));
3693 }
3694
3695 #[test]
3696 fn redact_event_refuses_a_target_in_a_different_room() {
3697 let mut conn = test_conn();
3698 make_room(&conn);
3699 let other_room = format!("!other:{}", matrix_server_name());
3700 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3701 .expect("other room");
3702 insert_timeline_event(&mut conn, "$target", &other_room, 1, "m.room.message", "{}", 1000).expect("target in other room");
3703
3704 let err = redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).unwrap_err();
3705 assert!(matches!(err, MatrixStoreError::WrongRoom(ref id) if id == "$target"));
3706 }
3707
3708 #[test]
3709 fn redact_event_refuses_create_and_encryption_events() {
3710 for event_type in ["m.room.create", "m.room.encryption"] {
3711 let mut conn = test_conn();
3712 make_room(&conn);
3713 apply_state_event(&mut conn, &StateEventWrite { event_id: "$target", room_id: ROOM, sender_user_id: 1, event_type, state_key: "", content: r#"{"a":1}"#, origin_server_ts: 1000, now: T0 }).expect("apply state event");
3714
3715 let err = redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).unwrap_err();
3716 assert!(
3717 matches!(err, MatrixStoreError::UnredactableEvent(ref t) if t == event_type),
3718 "event_type={event_type}"
3719 );
3720 }
3721 }
3722
3723 #[test]
3724 fn upsert_receipt_refuses_a_target_in_a_different_room() {
3725 let mut conn = test_conn();
3726 make_room(&conn);
3727 let other_room = format!("!other:{}", matrix_server_name());
3728 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3729 .expect("other room");
3730 let event = insert_timeline_event(&mut conn, "$e1", &other_room, 1, "m.room.message", "{}", 1000).expect("event in other room");
3731
3732 let err = upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &event.event_id, 5000).unwrap_err();
3733 assert!(matches!(err, MatrixStoreError::WrongRoom(ref id) if id == &event.event_id));
3734 }
3735
3736 #[test]
3739 fn mxid_parse_refuses_foreign_server() {
3740 assert_eq!(public_id_from_mxid("@abc123:example.org"), Ok("abc123"));
3741 assert_eq!(public_id_from_mxid("@abc123:otherserver.example"), Err(MatrixIdError::ForeignServerName));
3742 let aliases = ["chat.example", "m4a.example.net", "m4a.example.org"];
3744 set_local_aliases(aliases.iter().map(|s| s.to_string()));
3745 for name in aliases {
3746 assert_eq!(public_id_from_mxid(&format!("@abc123:{name}")), Ok("abc123"));
3747 }
3748 assert_eq!(public_id_from_mxid("@abc123:evil.example"), Err(MatrixIdError::ForeignServerName));
3749 assert_eq!(mxid_for_public_id("abc123"), format!("@abc123:{}", matrix_server_name()), "minting never uses an alias");
3750 assert_eq!(public_id_from_mxid("abc123:example.org"), Err(MatrixIdError::MissingSigil));
3751 assert_eq!(public_id_from_mxid("@abc123"), Err(MatrixIdError::MissingServerName));
3752 }
3753
3754 #[test]
3757 fn ensure_matrix_user_is_idempotent_and_refuses_a_reserved_localpart() {
3758 let conn = test_conn();
3759 let mxid = ensure_matrix_user(&conn, 1, "abc123", T0).expect("first ensure");
3760 assert_eq!(mxid, "@abc123:example.org");
3761 let mxid_again = ensure_matrix_user(&conn, 1, "abc123", T0).expect("second ensure is a no-op");
3762 assert_eq!(mxid_again, mxid);
3763 assert_eq!(mxid_of(&conn, 1).expect("mxid_of"), Some(mxid.clone()));
3764 assert_eq!(user_id_of(&conn, &mxid).expect("user_id_of"), Some(1));
3765
3766 let err = ensure_matrix_user(&conn, 2, "_bridge_evil", T0).unwrap_err();
3767 assert!(matches!(err, MatrixStoreError::ReservedLocalpart));
3768 }
3769
3770 #[test]
3771 fn apply_state_event_member_refreshes_room_members_and_power_levels() {
3772 let mut conn = test_conn();
3773 make_room(&conn);
3774 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3775 let bob = ensure_matrix_user(&conn, 2, "bob000000000000000000000000002", T0).expect("bob");
3776
3777 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins");
3778 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"invite"}"#, origin_server_ts: 1100, now: T0 }).expect("bob invited");
3779
3780 let joined = room_members(&conn, ROOM, Some(Membership::Join)).expect("joined members");
3781 assert_eq!(joined.len(), 1);
3782 assert_eq!(joined[0].user_id, 1);
3783 assert_eq!(joined[0].power_level, None);
3784
3785 let power_levels_content = serde_json::json!({"users": {alice.clone(): 100}, "users_default": 0}).to_string();
3786 apply_state_event(&mut conn, &StateEventWrite { event_id: "$pl1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.power_levels", state_key: "", content: &power_levels_content, origin_server_ts: 1200, now: T0 }).expect("power levels");
3787
3788 let alice_row = room_members(&conn, ROOM, None)
3789 .expect("all members")
3790 .into_iter()
3791 .find(|m| m.user_id == 1)
3792 .expect("alice row");
3793 assert_eq!(alice_row.power_level, Some(100));
3794
3795 let bob_rooms = rooms_for_user(&conn, 2, Some(Membership::Invite)).expect("bob's invited rooms");
3796 assert_eq!(bob_rooms, vec![ROOM.to_string()]);
3797 }
3798
3799 #[test]
3800 fn events_in_room_after_and_before_page_in_the_documented_order() {
3801 let mut conn = test_conn();
3802 make_room(&conn);
3803 let e1 = insert_timeline_event(&mut conn, "$e1", ROOM, 1, "m.room.message", "{}", 1000).expect("e1");
3804 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 1, "m.room.message", "{}", 2000).expect("e2");
3805 let e3 = insert_timeline_event(&mut conn, "$e3", ROOM, 1, "m.room.message", "{}", 3000).expect("e3");
3806
3807 let after = events_in_room_after(&conn, ROOM, e1.stream_id, 10).expect("after");
3808 assert_eq!(after.iter().map(|e| e.event_id.clone()).collect::<Vec<_>>(), vec![e2.event_id.clone(), e3.event_id.clone()]);
3809
3810 let before = events_in_room_before(&conn, ROOM, e3.stream_id, 10).expect("before");
3811 assert_eq!(before.iter().map(|e| e.event_id.clone()).collect::<Vec<_>>(), vec![e2.event_id.clone(), e1.event_id.clone()]);
3812
3813 assert_eq!(max_stream_id(&conn).expect("max"), e3.stream_id);
3814 }
3815
3816 #[test]
3817 fn txn_dedup_lookup_of_a_to_device_send_has_no_event_id() {
3818 let conn = test_conn();
3819 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-td").expect("lookup"), TxnDedupEntry::NotSeen);
3820 txn_dedup_record(&conn, 1, "DEV1", "txn-td", None, T0).expect("record to-device send");
3821 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-td").expect("lookup again"), TxnDedupEntry::Seen(None));
3822 }
3823
3824 #[test]
3825 fn filters_create_and_get_are_scoped_to_their_owner() {
3826 let conn = test_conn();
3827 let filter_id = create_filter(&conn, 1, r#"{"room":{"timeline":{"limit":20}}}"#).expect("create");
3828 assert_eq!(get_filter(&conn, 1, filter_id).expect("owner reads it"), Some(r#"{"room":{"timeline":{"limit":20}}}"#.to_string()));
3829 assert_eq!(get_filter(&conn, 2, filter_id).expect("a different user cannot"), None);
3830 }
3831
3832 #[test]
3833 fn legacy_dm_message_map_insert_and_get_round_trip() {
3834 let mut conn = test_conn();
3835 make_room(&conn);
3836 ensure_legacy_dm_map_table(&conn);
3837 let event = insert_timeline_event(&mut conn, "$legacy1", ROOM, 1, "org.example.legacy_dm", "{}", 1000).expect("insert");
3838 insert_legacy_dm_message_map(&conn, 42, &event.event_id).expect("map insert");
3839 assert_eq!(legacy_dm_message_event_id(&conn, 42).expect("map get"), Some(event.event_id));
3840 assert_eq!(legacy_dm_message_event_id(&conn, 999).expect("missing"), None);
3841 }
3842
3843 #[test]
3846 fn power_level_defaults_apply_when_fields_missing() {
3847 let pl = serde_json::json!({});
3848 assert_eq!(user_level(&pl, "@nobody:example.org"), 0, "users_default defaults to 0");
3849 assert_eq!(event_level(&pl, "m.room.message", false), 0, "events_default defaults to 0");
3850 assert_eq!(event_level(&pl, "m.room.name", true), 50, "state_default defaults to 50");
3851 for action in [PowerAction::Invite, PowerAction::Kick, PowerAction::Ban, PowerAction::Redact, PowerAction::StateDefault] {
3852 assert!(!can(&pl, action, "@nobody:example.org"), "level 0 must not reach the default 50 threshold for {action:?}");
3853 }
3854
3855 let pl_with_creator = serde_json::json!({ "users": { "@creator:example.org": 100 } });
3856 assert!(can(&pl_with_creator, PowerAction::Ban, "@creator:example.org"));
3857 assert!(can(&pl_with_creator, PowerAction::StateDefault, "@creator:example.org"));
3858 }
3859
3860 #[test]
3861 fn event_level_uses_the_events_type_override_before_falling_back_to_a_default() {
3862 let pl = serde_json::json!({ "events": { "m.room.name": 60 }, "events_default": 0, "state_default": 50 });
3863 assert_eq!(event_level(&pl, "m.room.name", true), 60, "an explicit events[type] override wins");
3864 assert_eq!(event_level(&pl, "m.room.topic", true), 50, "an unlisted state type falls back to state_default");
3865 assert_eq!(event_level(&pl, "m.room.message", false), 0, "an unlisted timeline type falls back to events_default");
3866 }
3867
3868 #[test]
3871 fn can_act_on_requires_strictly_greater_level_except_self_leave() {
3872 let pl = serde_json::json!({
3873 "users": { "@owner:example.org": 100, "@admin:example.org": 50, "@peer:example.org": 50 },
3874 "kick": 50,
3875 "ban": 50,
3876 });
3877
3878 assert!(!can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@owner:example.org", false));
3881 assert!(!can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@peer:example.org", false));
3883 assert!(can_act_on(&pl, PowerAction::Kick, "@owner:example.org", "@admin:example.org", false));
3885 assert!(can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@admin:example.org", true));
3888 assert!(!can_act_on(&pl, PowerAction::Ban, "@admin:example.org", "@admin:example.org", false));
3890 }
3891
3892 #[test]
3893 fn validate_power_levels_change_refuses_raising_self_above_own_level() {
3894 let old = serde_json::json!({ "users": { "@admin:example.org": 50 } });
3895 let new = serde_json::json!({ "users": { "@admin:example.org": 100 } });
3896 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3897 }
3898
3899 #[test]
3900 fn validate_power_levels_change_refuses_demoting_a_peer_at_an_equal_level() {
3901 let old = serde_json::json!({ "users": { "@a:example.org": 50, "@b:example.org": 50 } });
3902 let new = serde_json::json!({ "users": { "@a:example.org": 50, "@b:example.org": 0 } });
3903 assert!(validate_power_levels_change(&old, &new, "@a:example.org").is_err());
3904 }
3905
3906 #[test]
3907 fn validate_power_levels_change_allows_demoting_self() {
3908 let old = serde_json::json!({ "users": { "@admin:example.org": 50 } });
3909 let new = serde_json::json!({ "users": { "@admin:example.org": 10 } });
3910 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_ok());
3911 }
3912
3913 #[test]
3914 fn validate_power_levels_change_refuses_raising_events_default_above_own_level() {
3915 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "events_default": 0 });
3916 let new = serde_json::json!({ "users": { "@admin:example.org": 50 }, "events_default": 60 });
3917 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3918 }
3919
3920 #[test]
3921 fn validate_power_levels_change_allows_the_owner_changing_anything_up_to_their_own_level() {
3922 let old = serde_json::json!({ "users": { "@owner:example.org": 100, "@a:example.org": 50 } });
3923 let new = serde_json::json!({
3924 "users": { "@owner:example.org": 100, "@a:example.org": 90 },
3925 "ban": 100,
3926 "kick": 100,
3927 "events_default": 100,
3928 });
3929 assert!(validate_power_levels_change(&old, &new, "@owner:example.org").is_ok());
3930 }
3931
3932 #[test]
3933 fn validate_power_levels_change_refuses_a_scalar_field_change_above_own_level() {
3934 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 50 });
3935 let new = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 75 });
3936 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3937 }
3938
3939 #[test]
3940 fn validate_power_levels_change_ignores_unchanged_fields() {
3941 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 50, "events": { "m.room.name": 40 } });
3942 let new = old.clone();
3943 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_ok());
3944 }
3945
3946 fn bootstrap<'a>(room_id: &'a str, kind: RoomKind, creator: i64) -> RoomBootstrap<'a> {
3949 RoomBootstrap {
3950 room_id,
3951 kind,
3952 creator_user_id: creator,
3953 created_at: T0,
3954 is_encrypted: true,
3955 join_rule: if kind == RoomKind::Channel { JoinRule::Public } else { JoinRule::Invite },
3956 history_visibility: HistoryVisibility::Shared,
3957 dm_pair_key: None,
3958 legacy_dm_id: None,
3959 }
3960 }
3961
3962 fn state_event(event_id: &str, sender: i64, event_type: &str, state_key: &str, content: &str) -> NewStateEvent {
3963 NewStateEvent {
3964 event_id: event_id.to_string(),
3965 sender_user_id: sender,
3966 event_type: event_type.to_string(),
3967 state_key: state_key.to_string(),
3968 content: content.to_string(),
3969 }
3970 }
3971
3972 #[test]
3973 fn create_room_with_state_inserts_the_room_and_every_bootstrap_event_atomically() {
3974 let mut conn = test_conn();
3975 let creator_mxid = ensure_matrix_user(&conn, 1, "creator0000000000000000000001", T0).expect("creator");
3976 let room_id = "!batch:example.org";
3977
3978 let events = vec![
3979 state_event("$create", 1, "m.room.create", "", r#"{"room_version":"11"}"#),
3980 state_event("$m1", 1, "m.room.member", &creator_mxid, r#"{"membership":"join"}"#),
3981 state_event(
3982 "$pl",
3983 1,
3984 "m.room.power_levels",
3985 "",
3986 &serde_json::json!({"users": {creator_mxid.clone(): 100}, "users_default": 0}).to_string(),
3987 ),
3988 ];
3989
3990 let (room, applied) = create_room_with_state(&mut conn, bootstrap(room_id, RoomKind::Group, 1), &events, 1000).expect("create batch");
3991 assert_eq!(room.id, room_id);
3992 assert_eq!(applied.len(), 3);
3993 assert!(get_room(&conn, room_id).expect("get room").is_some());
3994 for event in &applied {
3995 assert!(get_event(&conn, &event.event_id).expect("get event").is_some());
3996 }
3997 let creator_row = room_member(&conn, room_id, 1).expect("member row").expect("row exists");
3998 assert_eq!(creator_row.power_level, Some(100), "power_levels applied after the member row existed");
3999 }
4000
4001 #[test]
4002 fn create_room_is_atomic_on_failure() {
4003 let mut conn = test_conn();
4004 let creator_mxid = ensure_matrix_user(&conn, 1, "creator0000000000000000000002", T0).expect("creator");
4005 let room_id = "!atomic:example.org";
4006
4007 let events = vec![
4008 state_event("$create", 1, "m.room.create", "", r#"{"room_version":"11"}"#),
4009 state_event("$m1", 1, "m.room.member", &creator_mxid, r#"{"membership":"join"}"#),
4010 state_event("$bad", 1, "m.room.member", "@ghost:example.org", r#"{"membership":"invite"}"#),
4013 ];
4014
4015 let err = create_room_with_state(&mut conn, bootstrap(room_id, RoomKind::Group, 1), &events, 1000).unwrap_err();
4016 assert!(matches!(err, MatrixStoreError::UnknownMxid(ref m) if m == "@ghost:example.org"));
4017
4018 assert_eq!(get_room(&conn, room_id).expect("get room"), None, "a failed batch must leave no room row");
4019 assert_eq!(get_event(&conn, "$create").expect("get"), None, "a failed batch must leave no event rows at all");
4020 assert_eq!(get_event(&conn, "$m1").expect("get"), None);
4021 }
4022
4023 #[test]
4026 fn dm_pair_key_reuse_is_freed_once_the_room_is_not_reused() {
4027 let conn = test_conn();
4028 create_room(&conn, "!dm1:example.org", RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None)
4029 .expect("first dm room");
4030 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup").map(|r| r.id), Some("!dm1:example.org".to_string()));
4031
4032 clear_dm_pair_key(&conn, "!dm1:example.org").expect("clear");
4033 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup after clear"), None);
4034
4035 create_room(&conn, "!dm2:example.org", RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None)
4037 .expect("second dm room reuses the freed pair key");
4038 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup").map(|r| r.id), Some("!dm2:example.org".to_string()));
4039 }
4040
4041 #[test]
4044 fn room_member_finds_the_one_row_forget_membership_deletes_only_when_left() {
4045 let mut conn = test_conn();
4046 make_room(&conn);
4047 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000099", T0).expect("alice");
4048 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("join");
4049
4050 assert_eq!(room_member(&conn, ROOM, 1).expect("member").map(|m| m.membership), Some(Membership::Join));
4051 assert_eq!(room_member(&conn, ROOM, 999).expect("no such member"), None);
4052
4053 assert_eq!(forget_membership(&conn, ROOM, 1).expect("forget while joined"), 0);
4056 assert!(room_member(&conn, ROOM, 1).expect("still a member").is_some());
4057
4058 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"leave"}"#, origin_server_ts: 2000, now: T0 }).expect("leave");
4059 assert_eq!(forget_membership(&conn, ROOM, 1).expect("forget after leaving"), 1);
4060 assert_eq!(room_member(&conn, ROOM, 1).expect("gone"), None);
4061 }
4062
4063 #[test]
4066 fn state_events_of_type_at_excludes_state_keys_created_after_the_cutoff() {
4067 let mut conn = test_conn();
4068 make_room(&conn);
4069 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000098", T0).expect("alice");
4070 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000098", T0).expect("bob");
4071
4072 let e1 = apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins");
4073 let cutoff = e1.stream_id;
4074 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"invite"}"#, origin_server_ts: 1100, now: T0 }).expect("bob invited later");
4075
4076 let at_cutoff = state_events_of_type_at(&conn, ROOM, "m.room.member", cutoff).expect("at cutoff");
4077 assert_eq!(at_cutoff.len(), 1, "bob's invite lands strictly after the cutoff and must be excluded");
4078 assert_eq!(at_cutoff[0].event_id, "$m1");
4079
4080 let after_both = state_events_of_type_at(&conn, ROOM, "m.room.member", cutoff + 1).expect("after both");
4081 assert_eq!(after_both.len(), 2);
4082 }
4083
4084 #[test]
4087 fn stripped_invite_state_includes_room_basics_and_the_inviters_own_member_event() {
4088 let mut conn = test_conn();
4089 make_room(&conn);
4090 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000097", T0).expect("alice");
4091 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000097", T0).expect("bob");
4092
4093 apply_state_event(&mut conn, &StateEventWrite { event_id: "$create", room_id: ROOM, sender_user_id: 1, event_type: "m.room.create", state_key: "", content: r#"{"room_version":"11"}"#, origin_server_ts: 900, now: T0 }).expect("create");
4094 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins");
4095 apply_state_event(&mut conn, &StateEventWrite { event_id: "$jr", room_id: ROOM, sender_user_id: 1, event_type: "m.room.join_rules", state_key: "", content: r#"{"join_rule":"invite"}"#, origin_server_ts: 1100, now: T0 }).expect("join rules");
4096 apply_state_event(&mut conn, &StateEventWrite { event_id: "$m2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"invite"}"#, origin_server_ts: 1200, now: T0 }).expect("bob invited");
4097
4098 let stripped = stripped_invite_state(&conn, ROOM, 1).expect("stripped state");
4099 let types: Vec<&str> = stripped.iter().map(|v| v["type"].as_str().expect("type")).collect();
4100 assert!(types.contains(&"m.room.create"));
4101 assert!(types.contains(&"m.room.join_rules"));
4102 assert!(!types.contains(&"m.room.encryption"), "no encryption event exists in this room");
4103
4104 let inviter_member = stripped
4105 .iter()
4106 .find(|v| v["type"] == "m.room.member" && v["state_key"] == alice)
4107 .expect("the inviter's own member event is included");
4108 assert_eq!(inviter_member["sender"], alice);
4109 assert_eq!(inviter_member["content"]["membership"], "join");
4110 }
4111
4112 #[test]
4115 fn user_ids_with_leave_transition_in_rooms_finds_only_leave_and_ban_inside_the_window() {
4116 let mut conn = test_conn();
4117 make_room(&conn);
4118 let alice = ensure_matrix_user(&conn, 2, "alice00000000000000000000000097", T0).expect("alice");
4119 let bob = ensure_matrix_user(&conn, 3, "bob0000000000000000000000000097", T0).expect("bob");
4120 let carol = ensure_matrix_user(&conn, 4, "carol0000000000000000000000097a", T0).expect("carol");
4121
4122 apply_state_event(&mut conn, &StateEventWrite { event_id: "$a1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins");
4123 let boundary = apply_state_event(&mut conn, &StateEventWrite { event_id: "$b1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"join"}"#, origin_server_ts: 1100, now: T0 })
4124 .expect("bob joins")
4125 .stream_id;
4126 apply_state_event(&mut conn, &StateEventWrite { event_id: "$a2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"leave"}"#, origin_server_ts: 1200, now: T0 }).expect("alice leaves");
4127 apply_state_event(&mut conn, &StateEventWrite { event_id: "$b2", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"ban"}"#, origin_server_ts: 1300, now: T0 }).expect("bob banned");
4128 apply_state_event(&mut conn, &StateEventWrite { event_id: "$c1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &carol, content: r#"{"membership":"join"}"#, origin_server_ts: 1400, now: T0 }).expect("carol joins (not a departure)");
4129
4130 let mut left = user_ids_with_leave_transition_in_rooms(&conn, &[ROOM.to_string()], boundary, i64::MAX).expect("query");
4131 left.sort_unstable();
4132 assert_eq!(left, vec![2, 3], "alice (leave) and bob (ban) both count; carol's join does not");
4133
4134 let empty = user_ids_with_leave_transition_in_rooms(&conn, &[], 0, i64::MAX).expect("empty room set");
4135 assert!(empty.is_empty());
4136 }
4137
4138 #[test]
4141 fn membership_at_reads_the_state_as_of_a_stream_position() {
4142 let mut conn = test_conn();
4143 make_room(&conn);
4144 let alice = ensure_matrix_user(&conn, 2, "alice00000000000000000000000081", T0).expect("alice");
4145 let bob = ensure_matrix_user(&conn, 3, "bob0000000000000000000000000081", T0).expect("bob");
4146
4147 let alice_joined = apply_state_event(&mut conn, &StateEventWrite { event_id: "$a1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins").stream_id;
4148 let bob_invited = apply_state_event(&mut conn, &StateEventWrite { event_id: "$b1", room_id: ROOM, sender_user_id: 1, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"invite"}"#, origin_server_ts: 1100, now: T0 }).expect("bob invited").stream_id;
4149 let bob_joined = apply_state_event(&mut conn, &StateEventWrite { event_id: "$b2", room_id: ROOM, sender_user_id: 3, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"join","displayname":"Bob"}"#, origin_server_ts: 1200, now: T0 }).expect("bob joins").stream_id;
4150
4151 assert_eq!(membership_at(&conn, ROOM, &bob, alice_joined).expect("query"), None, "bob had no member event yet");
4152 assert_eq!(membership_at(&conn, ROOM, &bob, bob_invited).expect("query"), Some(Membership::Invite));
4153 assert_eq!(membership_at(&conn, ROOM, &bob, bob_joined).expect("query"), Some(Membership::Join));
4154 assert_eq!(membership_at(&conn, ROOM, &alice, bob_invited).expect("query"), Some(Membership::Join));
4155
4156 let mut window = member_state_keys_in_window(&conn, ROOM, bob_invited, bob_joined).expect("window");
4157 window.sort();
4158 assert_eq!(window, vec![bob.clone()], "only bob has a member event after `bob_invited`");
4159 let mut all = member_state_keys_in_window(&conn, ROOM, 0, bob_joined).expect("whole history");
4160 all.sort();
4161 let mut expected = vec![alice, bob];
4162 expected.sort();
4163 assert_eq!(all, expected);
4164
4165 let rooms = [ROOM.to_string(), "!other:example.org".to_string()];
4166 assert_eq!(rooms_with_member_events_in_window(&conn, &rooms, bob_invited, bob_joined).expect("rooms"), vec![ROOM.to_string()]);
4167 assert!(rooms_with_member_events_in_window(&conn, &rooms, bob_joined, i64::MAX).expect("rooms after the last member event").is_empty());
4168 assert!(rooms_with_member_events_in_window(&conn, &[], 0, i64::MAX).expect("empty room set").is_empty());
4169 }
4170
4171 const ROOM_A: &str = "!roomA:example.org";
4174 const ROOM_B: &str = "!roomB:example.org";
4175 const ROOM_C: &str = "!roomC:example.org";
4176
4177 fn make_room_with_kind(conn: &Connection, room_id: &str, kind: RoomKind) {
4178 create_room(conn, room_id, kind, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None).expect("create room");
4179 }
4180
4181 fn member_content_in(conn: &Connection, room_id: &str, mxid: &str) -> serde_json::Value {
4182 let event = current_state_event(conn, room_id, "m.room.member", mxid).expect("query").expect("member event exists");
4183 serde_json::from_str(&event.content).expect("member content is json")
4184 }
4185
4186 fn member_event_count(conn: &Connection, room_id: &str, mxid: &str) -> i64 {
4187 conn.query_row(
4188 "SELECT COUNT(*) FROM events WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key = ?2",
4189 params![room_id, mxid],
4190 |row| row.get(0),
4191 )
4192 .expect("count member events")
4193 }
4194
4195 #[test]
4196 fn refresh_member_displayname_restamps_every_joined_room_and_skips_a_room_the_user_left() {
4197 let mut conn = test_conn();
4198 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000091", T0).expect("alice");
4199 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000091", T0).expect("bob");
4200 for room in [ROOM_A, ROOM_B, ROOM_C] {
4201 make_room_with_kind(&conn, room, RoomKind::Group);
4202 apply_state_event(&mut conn, &StateEventWrite { event_id: &new_event_id(), room_id: room, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join","displayname":"old_alice"}"#, origin_server_ts: 1000, now: T0 })
4203 .expect("alice joins");
4204 apply_state_event(&mut conn, &StateEventWrite { event_id: &new_event_id(), room_id: room, sender_user_id: 2, event_type: "m.room.member", state_key: &bob, content: r#"{"membership":"join","displayname":"bob_nick"}"#, origin_server_ts: 1100, now: T0 })
4205 .expect("bob joins");
4206 }
4207 apply_state_event(&mut conn, &StateEventWrite { event_id: &new_event_id(), room_id: ROOM_C, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"leave"}"#, origin_server_ts: 1200, now: T0 }).expect("alice leaves C");
4208
4209 let refresh = refresh_member_displayname(&mut conn, 1, "new_alice", T0, 5000).expect("refresh");
4210
4211 assert_eq!(refresh.rooms_updated, 2, "the two rooms alice is still joined in");
4212 for room in [ROOM_A, ROOM_B] {
4213 let content = member_content_in(&conn, room, &alice);
4214 assert_eq!(content["membership"], "join");
4215 assert_eq!(content["displayname"], "new_alice");
4216 assert_eq!(member_event_count(&conn, room, &alice), 2, "a NEW member event lands; the old one stays in history");
4217 let event = current_state_event(&conn, room, "m.room.member", &alice).expect("query").expect("exists");
4218 assert_eq!(event.sender_user_id, 1, "a join refresh is sent by the user themself");
4219 assert_eq!(event.origin_server_ts, 5000);
4220 assert_eq!(member_content_in(&conn, room, &bob)["displayname"], "bob_nick", "another member's event is never touched");
4221 }
4222 assert_eq!(member_content_in(&conn, ROOM_C, &alice), serde_json::json!({ "membership": "leave" }), "the left room gets no new event");
4223 assert_eq!(member_event_count(&conn, ROOM_C, &alice), 2, "join + leave, nothing more");
4224 assert_eq!(refresh.affected_user_ids, HashSet::from([1, 2]), "alice and bob are woken; room C's members are not part of it");
4225
4226 let repeat = refresh_member_displayname(&mut conn, 1, "new_alice", T0, 6000).expect("second pass");
4227 assert_eq!(repeat, DisplaynameRefresh::default(), "an up-to-date event is skipped, so a repeat pass writes nothing");
4228 assert_eq!(member_event_count(&conn, ROOM_A, &alice), 2);
4229 }
4230
4231 #[test]
4232 fn refresh_member_displayname_keeps_an_invite_events_sender_and_is_direct() {
4233 let mut conn = test_conn();
4234 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000092", T0).expect("alice");
4235 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000092", T0).expect("bob");
4236 make_room_with_kind(&conn, ROOM_A, RoomKind::Dm);
4237 apply_state_event(&mut conn, &StateEventWrite { event_id: &new_event_id(), room_id: ROOM_A, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join","displayname":"alice_nick"}"#, origin_server_ts: 1000, now: T0 })
4238 .expect("alice joins");
4239 apply_state_event(
4240 &mut conn,
4241 &StateEventWrite {
4242 event_id: &new_event_id(),
4243 room_id: ROOM_A,
4244 sender_user_id: 1,
4245 event_type: "m.room.member",
4246 state_key: &bob,
4247 content: r#"{"membership":"invite","is_direct":true}"#,
4248 origin_server_ts: 1100,
4249 now: T0,
4250 },
4251 )
4252 .expect("alice invites bob");
4253
4254 let refresh = refresh_member_displayname(&mut conn, 2, "bob_nick", T0, 5000).expect("refresh");
4255
4256 assert_eq!(refresh.rooms_updated, 1);
4257 let content = member_content_in(&conn, ROOM_A, &bob);
4258 assert_eq!(content, serde_json::json!({ "membership": "invite", "is_direct": true, "displayname": "bob_nick" }));
4259 let event = current_state_event(&conn, ROOM_A, "m.room.member", &bob).expect("query").expect("exists");
4260 assert_eq!(event.sender_user_id, 1, "the inviter stays the sender: stripped invite state reads the inviter off it");
4261 assert_eq!(room_member(&conn, ROOM_A, 2).expect("query").expect("row").membership, Membership::Invite, "still an invitation");
4262 assert_eq!(refresh.affected_user_ids, HashSet::from([1, 2]));
4263 }
4264
4265 #[test]
4266 fn refresh_member_displayname_is_a_noop_without_a_matrix_user_or_a_label() {
4267 let mut conn = test_conn();
4268 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000093", T0).expect("alice");
4269 make_room_with_kind(&conn, ROOM_A, RoomKind::Group);
4270 apply_state_event(&mut conn, &StateEventWrite { event_id: &new_event_id(), room_id: ROOM_A, sender_user_id: 1, event_type: "m.room.member", state_key: &alice, content: r#"{"membership":"join"}"#, origin_server_ts: 1000, now: T0 }).expect("alice joins");
4271
4272 assert_eq!(refresh_member_displayname(&mut conn, 99, "ghost", T0, 5000).expect("unknown user"), DisplaynameRefresh::default());
4273 assert_eq!(refresh_member_displayname(&mut conn, 1, "", T0, 5000).expect("empty label"), DisplaynameRefresh::default());
4274 assert_eq!(member_event_count(&conn, ROOM_A, &alice), 1);
4275 assert_eq!(matrix_user_ids(&conn).expect("ids"), vec![1]);
4276 }
4277
4278 fn native_dm_room(conn: &Connection) {
4281 create_room(conn, ROOM, RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None).expect("create dm room");
4282 }
4283
4284 fn legacy_import(legacy_message_id: i64, event_id: &str) -> LegacyDmMessageImport {
4285 LegacyDmMessageImport {
4286 legacy_message_id,
4287 event_id: event_id.to_string(),
4288 sender_user_id: 1,
4289 content: "{}".to_string(),
4290 origin_server_ts: 1000 + legacy_message_id,
4291 }
4292 }
4293
4294 fn adoption_of(legacy_dm_id: i64) -> DmAdoption<'static> {
4295 DmAdoption { room_id: ROOM, legacy_dm_id, key_events: &[], key_events_origin_server_ts: 500, now: T0 }
4296 }
4297
4298 #[test]
4299 fn adopt_dm_room_for_legacy_binds_the_room_and_imports_the_messages() {
4300 let mut conn = test_conn();
4301 ensure_legacy_dm_map_table(&conn);
4302 native_dm_room(&conn);
4303
4304 let counts = adopt_dm_room_for_legacy(&mut conn, adoption_of(7), &[legacy_import(1, "$l1"), legacy_import(2, "$l2")], &[])
4305 .expect("adopt")
4306 .expect("the room is adoptable");
4307 assert_eq!(counts.messages_imported, 2);
4308 assert_eq!(room_by_legacy_dm_id(&conn, 7).expect("query").expect("bound").id, ROOM);
4309 assert_eq!(highest_mapped_legacy_message_id(&conn, ROOM).expect("query"), Some(2));
4310 assert_eq!(get_event(&conn, "$l1").expect("query").expect("imported").room_id, ROOM);
4311 }
4312
4313 #[test]
4314 fn adopt_dm_room_for_legacy_writes_a_key_event_only_where_the_room_has_none() {
4315 let mut conn = test_conn();
4316 ensure_legacy_dm_map_table(&conn);
4317 native_dm_room(&conn);
4318 let existing = apply_state_event(&mut conn, &StateEventWrite { event_id: "$k-existing", room_id: ROOM, sender_user_id: 1, event_type: "org.example.legacy_dm_key", state_key: "@a:example.org", content: r#"{"public_key_b64":"AAAA"}"#, origin_server_ts: 900, now: T0 })
4319 .expect("existing key event");
4320 let key_events = [
4321 NewStateEvent {
4322 event_id: "$k-a".to_string(),
4323 sender_user_id: 1,
4324 event_type: "org.example.legacy_dm_key".to_string(),
4325 state_key: "@a:example.org".to_string(),
4326 content: r#"{"public_key_b64":"BBBB"}"#.to_string(),
4327 },
4328 NewStateEvent {
4329 event_id: "$k-b".to_string(),
4330 sender_user_id: 2,
4331 event_type: "org.example.legacy_dm_key".to_string(),
4332 state_key: "@b:example.org".to_string(),
4333 content: r#"{"public_key_b64":"CCCC"}"#.to_string(),
4334 },
4335 ];
4336 let adoption = DmAdoption { key_events: &key_events, ..adoption_of(7) };
4337 adopt_dm_room_for_legacy(&mut conn, adoption, &[], &[]).expect("adopt").expect("adoptable");
4338
4339 let a = current_state_event(&conn, ROOM, "org.example.legacy_dm_key", "@a:example.org").expect("query").expect("still present");
4340 assert_eq!(a.event_id, existing.event_id, "an existing key event is never overwritten");
4341 let b = current_state_event(&conn, ROOM, "org.example.legacy_dm_key", "@b:example.org").expect("query").expect("added");
4342 assert_eq!(b.event_id, "$k-b");
4343 }
4344
4345 #[test]
4346 fn adopt_dm_room_for_legacy_refuses_a_room_that_is_bound_or_not_a_dm() {
4347 let mut conn = test_conn();
4348 ensure_legacy_dm_map_table(&conn);
4349 make_room(&conn);
4350 assert!(adopt_dm_room_for_legacy(&mut conn, adoption_of(7), &[legacy_import(1, "$l1")], &[]).expect("adopt").is_none(), "a group room is never adopted");
4351 assert!(room_by_legacy_dm_id(&conn, 7).expect("query").is_none());
4352
4353 let mut bound = test_conn();
4354 create_room(&bound, ROOM, RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), Some(9)).expect("create bound room");
4355 assert!(adopt_dm_room_for_legacy(&mut bound, adoption_of(7), &[legacy_import(1, "$l1")], &[]).expect("adopt").is_none(), "an already-bound room is never re-bound");
4356 assert_eq!(room_by_legacy_dm_id(&bound, 9).expect("query").expect("still bound to 9").id, ROOM);
4357 assert!(get_event(&bound, "$l1").expect("query").is_none(), "a refused adoption writes nothing");
4358 }
4359
4360 #[test]
4361 fn adopt_dm_room_for_legacy_rolls_back_the_binding_when_an_import_fails() {
4362 let mut conn = test_conn();
4363 ensure_legacy_dm_map_table(&conn);
4364 native_dm_room(&conn);
4365
4366 let result = adopt_dm_room_for_legacy(&mut conn, adoption_of(7), &[legacy_import(1, "$dup"), legacy_import(2, "$dup")], &[]);
4369 assert!(result.is_err());
4370
4371 assert!(room_by_legacy_dm_id(&conn, 7).expect("query").is_none(), "the binding must roll back with the failed import");
4372 assert!(get_event(&conn, "$dup").expect("query").is_none(), "no imported event survives");
4373 assert_eq!(highest_mapped_legacy_message_id(&conn, ROOM).expect("query"), None);
4374 }
4375}