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 if name.is_empty() || name.contains(':') || name.contains('/') {
64 return Err("server name must be one hostname");
65 }
66 SERVER_NAME_CELL.set(name).map_err(|_| "server name already set")
67}
68
69static LOCAL_ALIASES: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
70
71
72pub fn set_local_aliases(names: impl IntoIterator<Item = String>) {
80 let list: Vec<String> = names.into_iter().map(|n| n.trim().to_ascii_lowercase()).filter(|n| !n.is_empty() && !n.contains(':') && !n.contains('/')).collect();
81 let _ = LOCAL_ALIASES.set(list);
82}
83
84pub fn is_local_server_name(name: &str) -> bool {
86 name == matrix_server_name() || LOCAL_ALIASES.get().is_some_and(|aliases| aliases.iter().any(|a| a.eq_ignore_ascii_case(name)))
87}
88
89pub fn matrix_server_name() -> &'static str {
91 SERVER_NAME_CELL.get_or_init(|| "example.org".to_string()).as_str()
92}
93
94pub const MATRIX_ROOM_VERSION: &str = "11";
99
100pub const MATRIX_EVENT_CONTENT_MAX_BYTES: usize = 16 * 1024;
106
107pub const RESERVED_LOCALPART_PREFIX: &str = "_bridge_";
112
113pub fn is_reserved_localpart(localpart: &str) -> bool {
117 localpart.starts_with(RESERVED_LOCALPART_PREFIX)
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum MatrixIdError {
123 MissingSigil,
125 MissingServerName,
127 ForeignServerName,
131}
132
133impl fmt::Display for MatrixIdError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 MatrixIdError::MissingSigil => write!(f, "mxid is missing its '@' sigil"),
137 MatrixIdError::MissingServerName => write!(f, "mxid is missing a ':server_name' suffix"),
138 MatrixIdError::ForeignServerName => write!(f, "mxid is addressed to a foreign server name"),
139 }
140 }
141}
142
143pub fn mxid_for_public_id(public_id: &str) -> String {
146 format!("@{public_id}:{}", matrix_server_name())
147}
148
149pub fn public_id_from_mxid(mxid: &str) -> Result<&str, MatrixIdError> {
153 let rest = mxid.strip_prefix('@').ok_or(MatrixIdError::MissingSigil)?;
154 let (localpart, server_name) = rest.split_once(':').ok_or(MatrixIdError::MissingServerName)?;
155 if !is_local_server_name(server_name) {
156 return Err(MatrixIdError::ForeignServerName);
157 }
158 Ok(localpart)
159}
160
161fn random_id_component() -> String {
164 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
165 use base64::Engine;
166 use rand::Rng;
167 let bytes: [u8; 16] = rand::thread_rng().gen();
168 URL_SAFE_NO_PAD.encode(bytes)
169}
170
171pub fn new_room_id() -> String {
173 format!("!{}:{}", random_id_component(), matrix_server_name())
174}
175
176pub fn new_event_id() -> String {
179 format!("${}", random_id_component())
180}
181
182#[derive(Debug)]
189pub enum MatrixStoreError {
190 Db(rusqlite::Error),
191 Json(serde_json::Error),
194 ReservedLocalpart,
197 DuplicateAnnotation,
201 UnknownEventId(String),
203 UnknownMxid(String),
206 InvalidMembership(String),
209 InvalidRelationTarget(String),
214 WrongRoom(String),
219 UnredactableEvent(String),
223}
224
225impl From<rusqlite::Error> for MatrixStoreError {
226 fn from(e: rusqlite::Error) -> Self {
227 MatrixStoreError::Db(e)
228 }
229}
230
231impl From<serde_json::Error> for MatrixStoreError {
232 fn from(e: serde_json::Error) -> Self {
233 MatrixStoreError::Json(e)
234 }
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum RoomKind {
246 Dm,
247 Group,
248 Channel,
249}
250
251impl RoomKind {
252 pub fn as_str(self) -> &'static str {
253 match self {
254 RoomKind::Dm => "dm",
255 RoomKind::Group => "group",
256 RoomKind::Channel => "channel",
257 }
258 }
259
260 pub fn from_wire_name(s: &str) -> Option<Self> {
261 match s {
262 "dm" => Some(RoomKind::Dm),
263 "group" => Some(RoomKind::Group),
264 "channel" => Some(RoomKind::Channel),
265 _ => None,
266 }
267 }
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum JoinRule {
272 Invite,
273 Public,
274}
275
276impl JoinRule {
277 pub fn as_str(self) -> &'static str {
278 match self {
279 JoinRule::Invite => "invite",
280 JoinRule::Public => "public",
281 }
282 }
283
284 pub fn from_wire_name(s: &str) -> Option<Self> {
285 match s {
286 "invite" => Some(JoinRule::Invite),
287 "public" => Some(JoinRule::Public),
288 _ => None,
289 }
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
294pub enum HistoryVisibility {
295 Shared,
296 WorldReadable,
297 Invited,
298 Joined,
299}
300
301impl HistoryVisibility {
302 pub fn as_str(self) -> &'static str {
303 match self {
304 HistoryVisibility::Shared => "shared",
305 HistoryVisibility::WorldReadable => "world_readable",
306 HistoryVisibility::Invited => "invited",
307 HistoryVisibility::Joined => "joined",
308 }
309 }
310
311 pub fn from_wire_name(s: &str) -> Option<Self> {
312 match s {
313 "shared" => Some(HistoryVisibility::Shared),
314 "world_readable" => Some(HistoryVisibility::WorldReadable),
315 "invited" => Some(HistoryVisibility::Invited),
316 "joined" => Some(HistoryVisibility::Joined),
317 _ => None,
318 }
319 }
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum Membership {
324 Join,
325 Invite,
326 Leave,
327 Ban,
328}
329
330impl Membership {
331 pub fn as_str(self) -> &'static str {
332 match self {
333 Membership::Join => "join",
334 Membership::Invite => "invite",
335 Membership::Leave => "leave",
336 Membership::Ban => "ban",
337 }
338 }
339
340 pub fn from_wire_name(s: &str) -> Option<Self> {
341 match s {
342 "join" => Some(Membership::Join),
343 "invite" => Some(Membership::Invite),
344 "leave" => Some(Membership::Leave),
345 "ban" => Some(Membership::Ban),
346 _ => None,
347 }
348 }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum ReceiptType {
353 Read,
354 ReadPrivate,
355}
356
357impl ReceiptType {
358 pub fn as_str(self) -> &'static str {
359 match self {
360 ReceiptType::Read => "m.read",
361 ReceiptType::ReadPrivate => "m.read.private",
362 }
363 }
364
365 pub fn from_wire_name(s: &str) -> Option<Self> {
366 match s {
367 "m.read" => Some(ReceiptType::Read),
368 "m.read.private" => Some(ReceiptType::ReadPrivate),
369 _ => None,
370 }
371 }
372}
373
374fn decode_enum<T>(idx: usize, column: &'static str, raw: &str, parse: fn(&str) -> Option<T>) -> rusqlite::Result<T> {
380 parse(raw).ok_or_else(|| rusqlite::Error::InvalidColumnType(idx, column.to_string(), rusqlite::types::Type::Text))
381}
382
383pub fn create_matrix_schema(conn: &Connection) -> rusqlite::Result<()> {
400 conn.execute_batch(
401 r#"
402 -- Global stream ordering — see this module's doc comment on the
403 -- single-writer guarantee that makes `UPDATE ... RETURNING` safe.
404 CREATE TABLE IF NOT EXISTS stream_counter (
405 id INTEGER PRIMARY KEY CHECK (id = 1),
406 value INTEGER NOT NULL
407 );
408 INSERT OR IGNORE INTO stream_counter (id, value) VALUES (1, 0);
409
410 -- Federation F0: this server's signing keys and the verify keys
411 -- cached from remote servers. Secrets live only in the (encrypted) DB.
412 CREATE TABLE IF NOT EXISTS fed_signing_keys (
413 key_id TEXT PRIMARY KEY,
414 secret BLOB NOT NULL,
415 created_ms INTEGER NOT NULL,
416 retired_ms INTEGER
417 );
418 CREATE TABLE IF NOT EXISTS fed_remote_keys (
419 server_name TEXT NOT NULL,
420 key_id TEXT NOT NULL,
421 public_key TEXT NOT NULL,
422 valid_until_ms INTEGER NOT NULL,
423 fetched_ms INTEGER NOT NULL,
424 PRIMARY KEY (server_name, key_id)
425 );
426
427 -- user_id -> mxid, filled on first touch by ensure_matrix_user
428 -- (plan §2 manager decision: new table, not in the original DDL
429 -- text). public_id is immutable, so this mapping never changes.
430 CREATE TABLE IF NOT EXISTS matrix_users (
431 user_id INTEGER PRIMARY KEY,
432 mxid TEXT NOT NULL UNIQUE,
433 created_at TEXT NOT NULL,
434 nick TEXT
435 );
436 CREATE UNIQUE INDEX IF NOT EXISTS idx_matrix_users_nick_lower
437 ON matrix_users(LOWER(nick)) WHERE nick IS NOT NULL;
438
439 -- Nick belongs to a session, not to matrix_users and not to the
440 -- device. device_id is only a mark. One device may have many sessions.
441 -- matrix_users.nick stays for old databases and is not read.
442 CREATE TABLE IF NOT EXISTS messenger_sessions (
443 session_id TEXT PRIMARY KEY,
444 user_id INTEGER NOT NULL,
445 device_id TEXT NOT NULL,
446 nick TEXT NOT NULL
447 );
448 CREATE INDEX IF NOT EXISTS idx_messenger_sessions_user
449 ON messenger_sessions(user_id);
450 CREATE UNIQUE INDEX IF NOT EXISTS idx_messenger_sessions_nick_lower
451 ON messenger_sessions(LOWER(nick));
452
453 CREATE TABLE IF NOT EXISTS rooms (
454 id TEXT PRIMARY KEY,
455 kind TEXT NOT NULL,
456 room_version TEXT NOT NULL DEFAULT '11',
457 creator_user_id INTEGER NOT NULL,
458 created_at TEXT NOT NULL,
459 is_encrypted INTEGER NOT NULL DEFAULT 0,
460 join_rule TEXT NOT NULL DEFAULT 'invite',
461 history_visibility TEXT NOT NULL DEFAULT 'shared',
462 dm_pair_key TEXT UNIQUE,
463 legacy_dm_id INTEGER UNIQUE
464 );
465 CREATE INDEX IF NOT EXISTS idx_rooms_kind ON rooms(kind);
466
467 CREATE TABLE IF NOT EXISTS events (
468 stream_id INTEGER PRIMARY KEY,
469 event_id TEXT NOT NULL UNIQUE,
470 room_id TEXT NOT NULL REFERENCES rooms(id),
471 sender_user_id INTEGER NOT NULL,
472 event_type TEXT NOT NULL,
473 state_key TEXT,
474 content TEXT NOT NULL,
475 origin_server_ts INTEGER NOT NULL,
476 txn_id TEXT,
477 redacts TEXT REFERENCES events(event_id),
478 redacted_by TEXT REFERENCES events(event_id)
479 );
480 CREATE INDEX IF NOT EXISTS idx_events_room_stream ON events(room_id, stream_id);
481 CREATE INDEX IF NOT EXISTS idx_events_room_type_state ON events(room_id, event_type, state_key);
482 CREATE INDEX IF NOT EXISTS idx_events_sender ON events(sender_user_id, stream_id);
483
484 CREATE TABLE IF NOT EXISTS current_state (
485 room_id TEXT NOT NULL REFERENCES rooms(id),
486 event_type TEXT NOT NULL,
487 state_key TEXT NOT NULL,
488 event_id TEXT NOT NULL REFERENCES events(event_id),
489 PRIMARY KEY (room_id, event_type, state_key)
490 );
491
492 CREATE TABLE IF NOT EXISTS room_members (
493 room_id TEXT NOT NULL REFERENCES rooms(id),
494 user_id INTEGER NOT NULL,
495 membership TEXT NOT NULL,
496 power_level INTEGER,
497 updated_at TEXT NOT NULL,
498 PRIMARY KEY (room_id, user_id)
499 );
500 CREATE INDEX IF NOT EXISTS idx_room_members_user ON room_members(user_id, membership);
501
502 CREATE TABLE IF NOT EXISTS relations (
503 event_id TEXT PRIMARY KEY REFERENCES events(event_id),
504 room_id TEXT NOT NULL REFERENCES rooms(id),
505 rel_type TEXT NOT NULL,
506 target_id TEXT NOT NULL REFERENCES events(event_id),
507 agg_key TEXT
508 );
509 CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_id, rel_type);
510
511 CREATE TABLE IF NOT EXISTS receipts (
512 room_id TEXT NOT NULL REFERENCES rooms(id),
513 user_id INTEGER NOT NULL,
514 receipt_type TEXT NOT NULL,
515 event_id TEXT NOT NULL REFERENCES events(event_id),
516 ts INTEGER NOT NULL,
517 stream_id INTEGER NOT NULL,
518 PRIMARY KEY (room_id, user_id, receipt_type)
519 );
520 CREATE INDEX IF NOT EXISTS idx_receipts_room_stream ON receipts(room_id, stream_id);
521
522 -- DEVIATION from plan §2's literal text: room_id has no
523 -- `REFERENCES rooms(id)` — see this function's doc comment.
524 CREATE TABLE IF NOT EXISTS account_data (
525 user_id INTEGER NOT NULL,
526 room_id TEXT NOT NULL DEFAULT '',
527 data_type TEXT NOT NULL,
528 content TEXT NOT NULL,
529 stream_id INTEGER NOT NULL,
530 PRIMARY KEY (user_id, room_id, data_type)
531 );
532 CREATE INDEX IF NOT EXISTS idx_account_data_user_stream ON account_data(user_id, stream_id);
533
534 CREATE TABLE IF NOT EXISTS txn_dedup (
535 user_id INTEGER NOT NULL,
536 device_id TEXT NOT NULL,
537 txn_id TEXT NOT NULL,
538 event_id TEXT REFERENCES events(event_id),
539 created_at TEXT NOT NULL,
540 PRIMARY KEY (user_id, device_id, txn_id)
541 );
542
543 CREATE TABLE IF NOT EXISTS filters (
544 id INTEGER PRIMARY KEY AUTOINCREMENT,
545 user_id INTEGER NOT NULL,
546 definition TEXT NOT NULL
547 );
548
549 -- legacy_dm_message_map removed (M2); drop_legacy_dm_scaffold_if_empty cleans old DBs
550 "#,
551 )?;
552 crate::public_channels::create_public_schema(conn)?;
554 crate::public_forum::create_forum_schema(conn)?;
555 crate::media::create_media_schema(conn)?;
556 crate::fed_rooms::create_fed_schema(conn)?;
557 crate::identities::create_identities_schema(conn)
558}
559
560pub fn parse_db_key(key_hex: &str) -> Result<[u8; 32], String> {
562 let h = key_hex.trim();
563 if h.len() != 64 || !h.bytes().all(|b| b.is_ascii_hexdigit()) {
564 return Err("database key must be 64 hex characters (32 bytes)".into());
565 }
566 let mut key = [0u8; 32];
567 for (i, b) in key.iter_mut().enumerate() {
568 *b = u8::from_str_radix(&h[2 * i..2 * i + 2], 16).map_err(|_| "database key must be hex".to_string())?;
569 }
570 Ok(key)
571}
572
573pub fn messenger_db_config(path: &str, key_hex: &str) -> Result<tesserax_store::DbConfig, String> {
575 let key = parse_db_key(key_hex)?;
576 Ok(tesserax_store::DbConfig::encrypted_native(path, std::sync::Arc::new(tesserax_store::keysource::StaticKeySource(key))))
577}
578
579pub fn open_messenger_db(path: &str, key_hex: &str) -> Result<tesserax_store::Db, String> {
582 let cfg = messenger_db_config(path, key_hex)?;
583 let db = tesserax_store::Db::open(&cfg).map_err(|e| e.to_string())?;
584 db.blocking(|conn| ensure_schema(conn)).map_err(|e| e.to_string())?;
585 Ok(db)
586}
587
588pub fn open_read_pool(path: &str, key_hex: &str, size: usize) -> Result<tesserax_store::ReadPool, String> {
590 let cfg = messenger_db_config(path, key_hex)?;
591 tesserax_store::ReadPoolConfig::from_config(cfg).pool_size(size.max(1)).open().map_err(|e| e.to_string())
592}
593
594pub fn ensure_schema(conn: &Connection) -> rusqlite::Result<()> {
596 create_matrix_schema(conn)?;
597 crate::keys::create_matrix_keys_schema(conn)?;
598 crate::retention::create_retention_schema(conn)?;
599 crate::public_channels::create_public_schema(conn)?;
600 Ok(())
601}
602
603pub(crate) fn next_stream_id(tx: &Transaction) -> rusqlite::Result<i64> {
615 tx.query_row("UPDATE stream_counter SET value = value + 1 WHERE id = 1 RETURNING value", [], |row| row.get(0))
616}
617
618pub fn max_stream_id(conn: &Connection) -> rusqlite::Result<i64> {
622 conn.query_row("SELECT value FROM stream_counter WHERE id = 1", [], |row| row.get(0))
623}
624
625pub fn ensure_matrix_user(conn: &Connection, user_id: i64, public_id: &str, now: &str) -> Result<String, MatrixStoreError> {
633 if is_reserved_localpart(public_id) {
634 return Err(MatrixStoreError::ReservedLocalpart);
635 }
636 let mxid = mxid_for_public_id(public_id);
637 conn.execute(
638 "INSERT INTO matrix_users (user_id, mxid, created_at) VALUES (?1, ?2, ?3)
639 ON CONFLICT(user_id) DO NOTHING",
640 params![user_id, mxid, now],
641 )?;
642 Ok(mxid)
643}
644
645pub fn mxid_of(conn: &Connection, user_id: i64) -> rusqlite::Result<Option<String>> {
647 conn.query_row("SELECT mxid FROM matrix_users WHERE user_id = ?1", params![user_id], |row| row.get(0))
648 .optional()
649}
650
651pub fn user_id_of(conn: &Connection, mxid: &str) -> rusqlite::Result<Option<i64>> {
653 conn.query_row("SELECT user_id FROM matrix_users WHERE mxid = ?1", params![mxid], |row| row.get(0))
654 .optional()
655}
656
657#[derive(Debug, Clone, PartialEq)]
662pub struct Room {
663 pub id: String,
664 pub kind: RoomKind,
665 pub room_version: String,
666 pub creator_user_id: i64,
667 pub created_at: String,
668 pub is_encrypted: bool,
669 pub join_rule: JoinRule,
670 pub history_visibility: HistoryVisibility,
671 pub dm_pair_key: Option<String>,
672 pub legacy_dm_id: Option<i64>,
673}
674
675const ROOM_SELECT_COLUMNS: &str =
676 "id, kind, room_version, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id";
677
678fn room_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Room> {
679 let kind_raw: String = row.get(1)?;
680 let join_rule_raw: String = row.get(6)?;
681 let history_visibility_raw: String = row.get(7)?;
682 Ok(Room {
683 id: row.get(0)?,
684 kind: decode_enum(1, "kind", &kind_raw, RoomKind::from_wire_name)?,
685 room_version: row.get(2)?,
686 creator_user_id: row.get(3)?,
687 created_at: row.get(4)?,
688 is_encrypted: row.get(5)?,
689 join_rule: decode_enum(6, "join_rule", &join_rule_raw, JoinRule::from_wire_name)?,
690 history_visibility: decode_enum(7, "history_visibility", &history_visibility_raw, HistoryVisibility::from_wire_name)?,
691 dm_pair_key: row.get(8)?,
692 legacy_dm_id: row.get(9)?,
693 })
694}
695
696#[allow(clippy::too_many_arguments)]
703fn insert_room_row(
704 conn: &Connection,
705 room_id: &str,
706 kind: RoomKind,
707 creator_user_id: i64,
708 created_at: &str,
709 is_encrypted: bool,
710 join_rule: JoinRule,
711 history_visibility: HistoryVisibility,
712 dm_pair_key: Option<&str>,
713 legacy_dm_id: Option<i64>,
714) -> rusqlite::Result<()> {
715 conn.execute(
716 "INSERT INTO rooms (id, kind, room_version, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id)
717 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
718 params![
719 room_id,
720 kind.as_str(),
721 MATRIX_ROOM_VERSION,
722 creator_user_id,
723 created_at,
724 is_encrypted,
725 join_rule.as_str(),
726 history_visibility.as_str(),
727 dm_pair_key,
728 legacy_dm_id,
729 ],
730 )?;
731 Ok(())
732}
733
734#[allow(clippy::too_many_arguments)]
741pub fn create_room(
742 conn: &Connection,
743 room_id: &str,
744 kind: RoomKind,
745 creator_user_id: i64,
746 created_at: &str,
747 is_encrypted: bool,
748 join_rule: JoinRule,
749 history_visibility: HistoryVisibility,
750 dm_pair_key: Option<&str>,
751 legacy_dm_id: Option<i64>,
752) -> rusqlite::Result<()> {
753 insert_room_row(conn, room_id, kind, creator_user_id, created_at, is_encrypted, join_rule, history_visibility, dm_pair_key, legacy_dm_id)
754}
755
756pub fn get_room(conn: &Connection, room_id: &str) -> rusqlite::Result<Option<Room>> {
757 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE id = ?1"), params![room_id], room_from_row)
758 .optional()
759}
760
761pub fn room_by_dm_pair_key(conn: &Connection, pair_key: &str) -> rusqlite::Result<Option<Room>> {
766 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE dm_pair_key = ?1"), params![pair_key], room_from_row)
767 .optional()
768}
769
770pub fn room_by_legacy_dm_id(conn: &Connection, legacy_dm_id: i64) -> rusqlite::Result<Option<Room>> {
775 conn.query_row(&format!("SELECT {ROOM_SELECT_COLUMNS} FROM rooms WHERE legacy_dm_id = ?1"), params![legacy_dm_id], room_from_row)
776 .optional()
777}
778
779pub fn clear_dm_pair_key(conn: &Connection, room_id: &str) -> rusqlite::Result<()> {
785 conn.execute("UPDATE rooms SET dm_pair_key = NULL WHERE id = ?1", params![room_id])?;
786 Ok(())
787}
788
789#[derive(Debug, Clone, PartialEq)]
794pub struct MatrixEvent {
795 pub stream_id: i64,
796 pub event_id: String,
797 pub room_id: String,
798 pub sender_user_id: i64,
799 pub event_type: String,
800 pub state_key: Option<String>,
802 pub content: String,
803 pub origin_server_ts: i64,
804 pub txn_id: Option<String>,
805 pub redacts: Option<String>,
806 pub redacted_by: Option<String>,
807}
808
809const EVENT_SELECT_COLUMNS: &str =
810 "stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id, redacts, redacted_by";
811
812const 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";
813
814fn event_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<MatrixEvent> {
815 Ok(MatrixEvent {
816 stream_id: row.get(0)?,
817 event_id: row.get(1)?,
818 room_id: row.get(2)?,
819 sender_user_id: row.get(3)?,
820 event_type: row.get(4)?,
821 state_key: row.get(5)?,
822 content: row.get(6)?,
823 origin_server_ts: row.get(7)?,
824 txn_id: row.get(8)?,
825 redacts: row.get(9)?,
826 redacted_by: row.get(10)?,
827 })
828}
829
830fn collect_events(rows: &mut rusqlite::Rows<'_>) -> rusqlite::Result<Vec<MatrixEvent>> {
831 let mut out = Vec::new();
832 while let Some(row) = rows.next()? {
833 out.push(event_from_row(row)?);
834 }
835 Ok(out)
836}
837
838#[derive(Debug, Clone, Copy)]
842struct TimelineEventRow<'a> {
843 event_id: &'a str,
844 room_id: &'a str,
845 sender_user_id: i64,
846 event_type: &'a str,
847 content: &'a str,
848 origin_server_ts: i64,
849 txn_id: Option<&'a str>,
850}
851
852fn insert_timeline_event_in_tx(tx: &Transaction, row: &TimelineEventRow<'_>) -> Result<MatrixEvent, MatrixStoreError> {
861 let TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id } = *row;
862 let stream_id = next_stream_id(tx)?;
863 tx.execute(
864 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id)
865 VALUES (?1, ?2, ?3, ?4, ?5, NULL, ?6, ?7, ?8)",
866 params![stream_id, event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id],
867 )?;
868 populate_relations(tx, event_id, room_id, sender_user_id, content)?;
869 Ok(MatrixEvent {
870 stream_id,
871 event_id: event_id.to_string(),
872 room_id: room_id.to_string(),
873 sender_user_id,
874 event_type: event_type.to_string(),
875 state_key: None,
876 content: content.to_string(),
877 origin_server_ts,
878 txn_id: txn_id.map(str::to_string),
879 redacts: None,
880 redacted_by: None,
881 })
882}
883
884pub fn insert_timeline_event(
893 conn: &mut Connection,
894 event_id: &str,
895 room_id: &str,
896 sender_user_id: i64,
897 event_type: &str,
898 content: &str,
899 origin_server_ts: i64,
900) -> Result<MatrixEvent, MatrixStoreError> {
901 let tx = conn.transaction()?;
902 let row = TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id: None };
903 let event = insert_timeline_event_in_tx(&tx, &row)?;
904 tx.commit()?;
905 Ok(event)
906}
907
908#[derive(Debug, Clone, PartialEq)]
914pub enum DedupedWrite {
915 New(MatrixEvent),
916 Existing(MatrixEvent),
917}
918
919#[allow(clippy::too_many_arguments)]
929pub fn insert_timeline_event_deduped(
930 conn: &mut Connection,
931 device_id: &str,
932 txn_id: &str,
933 event_id: &str,
934 room_id: &str,
935 sender_user_id: i64,
936 event_type: &str,
937 content: &str,
938 origin_server_ts: i64,
939 now: &str,
940) -> Result<DedupedWrite, MatrixStoreError> {
941 let tx = conn.transaction()?;
942 if let TxnDedupEntry::Seen(existing_event_id) = txn_dedup_lookup(&tx, sender_user_id, device_id, txn_id)? {
943 let existing_event_id = existing_event_id.ok_or_else(|| MatrixStoreError::UnknownEventId(txn_id.to_string()))?;
944 let event = get_event(&tx, &existing_event_id)?.ok_or_else(|| MatrixStoreError::UnknownEventId(existing_event_id.clone()))?;
945 tx.commit()?;
946 return Ok(DedupedWrite::Existing(event));
947 }
948 let row = TimelineEventRow { event_id, room_id, sender_user_id, event_type, content, origin_server_ts, txn_id: Some(txn_id) };
949 let event = insert_timeline_event_in_tx(&tx, &row)?;
950 txn_dedup_record(&tx, sender_user_id, device_id, txn_id, Some(event_id), now)?;
951 tx.commit()?;
952 Ok(DedupedWrite::New(event))
953}
954
955#[allow(clippy::too_many_arguments)]
963fn apply_state_event_in_tx(
964 tx: &Transaction,
965 event_id: &str,
966 room_id: &str,
967 sender_user_id: i64,
968 event_type: &str,
969 state_key: &str,
970 content: &str,
971 origin_server_ts: i64,
972 now: &str,
973) -> Result<MatrixEvent, MatrixStoreError> {
974 let stream_id = next_stream_id(tx)?;
975 tx.execute(
976 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id)
977 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL)",
978 params![stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts],
979 )?;
980 tx.execute(
981 "INSERT INTO current_state (room_id, event_type, state_key, event_id) VALUES (?1, ?2, ?3, ?4)
982 ON CONFLICT(room_id, event_type, state_key) DO UPDATE SET event_id = excluded.event_id",
983 params![room_id, event_type, state_key, event_id],
984 )?;
985 if event_type == "m.room.member" {
986 refresh_room_member(tx, room_id, state_key, content, now)?;
987 }
988 if event_type == "m.room.power_levels" {
989 refresh_power_levels(tx, room_id, content)?;
990 }
991 Ok(MatrixEvent {
992 stream_id,
993 event_id: event_id.to_string(),
994 room_id: room_id.to_string(),
995 sender_user_id,
996 event_type: event_type.to_string(),
997 state_key: Some(state_key.to_string()),
998 content: content.to_string(),
999 origin_server_ts,
1000 txn_id: None,
1001 redacts: None,
1002 redacted_by: None,
1003 })
1004}
1005
1006#[derive(Debug, Clone, Copy)]
1011pub struct StateEventWrite<'a> {
1012 pub event_id: &'a str,
1013 pub room_id: &'a str,
1014 pub sender_user_id: i64,
1015 pub event_type: &'a str,
1016 pub state_key: &'a str,
1017 pub content: &'a str,
1018 pub origin_server_ts: i64,
1019 pub now: &'a str,
1020}
1021
1022pub fn apply_state_event(conn: &mut Connection, write: &StateEventWrite<'_>) -> Result<MatrixEvent, MatrixStoreError> {
1027 let tx = conn.transaction()?;
1028 let event = apply_state_event_in_tx(
1029 &tx,
1030 write.event_id,
1031 write.room_id,
1032 write.sender_user_id,
1033 write.event_type,
1034 write.state_key,
1035 write.content,
1036 write.origin_server_ts,
1037 write.now,
1038 )?;
1039 tx.commit()?;
1040 Ok(event)
1041}
1042
1043#[derive(Debug, Clone, Default, PartialEq, Eq)]
1045pub struct DisplaynameRefresh {
1046 pub rooms_updated: usize,
1048 pub affected_user_ids: HashSet<i64>,
1051}
1052
1053pub fn refresh_member_displayname(
1071 conn: &mut Connection,
1072 user_id: i64,
1073 displayname: &str,
1074 now: &str,
1075 origin_server_ts: i64,
1076) -> Result<DisplaynameRefresh, MatrixStoreError> {
1077 let mut outcome = DisplaynameRefresh::default();
1078 if displayname.is_empty() {
1079 return Ok(outcome);
1080 }
1081 let Some(mxid) = mxid_of(conn, user_id)? else {
1082 return Ok(outcome);
1083 };
1084
1085 let tx = conn.transaction()?;
1086 for membership in [Membership::Join, Membership::Invite] {
1087 for room_id in rooms_for_user(&tx, user_id, Some(membership))? {
1088 let Some(current) = current_state_event(&tx, &room_id, "m.room.member", &mxid)? else {
1089 continue;
1090 };
1091 let mut content: serde_json::Value = serde_json::from_str(¤t.content)?;
1092 if content.get("displayname").and_then(|v| v.as_str()) == Some(displayname) {
1093 continue;
1094 }
1095 let Some(fields) = content.as_object_mut() else {
1096 continue;
1097 };
1098 fields.insert("displayname".to_string(), serde_json::Value::String(displayname.to_string()));
1099
1100 let sender_user_id = if membership == Membership::Join { user_id } else { current.sender_user_id };
1101 apply_state_event_in_tx(
1102 &tx,
1103 &new_event_id(),
1104 &room_id,
1105 sender_user_id,
1106 "m.room.member",
1107 &mxid,
1108 &content.to_string(),
1109 origin_server_ts,
1110 now,
1111 )?;
1112 outcome.rooms_updated += 1;
1113 outcome.affected_user_ids.insert(user_id);
1114 for member in room_members(&tx, &room_id, None)? {
1115 if matches!(member.membership, Membership::Join | Membership::Invite) {
1116 outcome.affected_user_ids.insert(member.user_id);
1117 }
1118 }
1119 }
1120 }
1121 tx.commit()?;
1122 Ok(outcome)
1123}
1124
1125pub fn matrix_user_ids(conn: &Connection) -> rusqlite::Result<Vec<i64>> {
1129 let mut stmt = conn.prepare("SELECT user_id FROM matrix_users ORDER BY user_id")?;
1130 let rows = stmt.query_map([], |row| row.get(0))?;
1131 rows.collect()
1132}
1133
1134#[derive(Debug, Clone, PartialEq)]
1144pub struct NewStateEvent {
1145 pub event_id: String,
1146 pub sender_user_id: i64,
1147 pub event_type: String,
1148 pub state_key: String,
1149 pub content: String,
1150}
1151
1152#[derive(Debug, Clone, Copy)]
1158pub struct RoomBootstrap<'a> {
1159 pub room_id: &'a str,
1160 pub kind: RoomKind,
1161 pub creator_user_id: i64,
1162 pub created_at: &'a str,
1163 pub is_encrypted: bool,
1164 pub join_rule: JoinRule,
1165 pub history_visibility: HistoryVisibility,
1166 pub dm_pair_key: Option<&'a str>,
1167 pub legacy_dm_id: Option<i64>,
1168}
1169
1170pub fn create_room_with_state(
1181 conn: &mut Connection,
1182 bootstrap: RoomBootstrap<'_>,
1183 state_events: &[NewStateEvent],
1184 origin_server_ts: i64,
1185) -> Result<(Room, Vec<MatrixEvent>), MatrixStoreError> {
1186 let tx = conn.transaction()?;
1187 insert_room_row(
1188 &tx,
1189 bootstrap.room_id,
1190 bootstrap.kind,
1191 bootstrap.creator_user_id,
1192 bootstrap.created_at,
1193 bootstrap.is_encrypted,
1194 bootstrap.join_rule,
1195 bootstrap.history_visibility,
1196 bootstrap.dm_pair_key,
1197 bootstrap.legacy_dm_id,
1198 )?;
1199
1200 let mut applied = Vec::with_capacity(state_events.len());
1201 for event in state_events {
1202 applied.push(apply_state_event_in_tx(
1203 &tx,
1204 &event.event_id,
1205 bootstrap.room_id,
1206 event.sender_user_id,
1207 &event.event_type,
1208 &event.state_key,
1209 &event.content,
1210 origin_server_ts,
1211 bootstrap.created_at,
1212 )?);
1213 }
1214 tx.commit()?;
1215
1216 Ok((
1217 Room {
1218 id: bootstrap.room_id.to_string(),
1219 kind: bootstrap.kind,
1220 room_version: MATRIX_ROOM_VERSION.to_string(),
1221 creator_user_id: bootstrap.creator_user_id,
1222 created_at: bootstrap.created_at.to_string(),
1223 is_encrypted: bootstrap.is_encrypted,
1224 join_rule: bootstrap.join_rule,
1225 history_visibility: bootstrap.history_visibility,
1226 dm_pair_key: bootstrap.dm_pair_key.map(str::to_string),
1227 legacy_dm_id: bootstrap.legacy_dm_id,
1228 },
1229 applied,
1230 ))
1231}
1232
1233fn refresh_room_member(tx: &Transaction, room_id: &str, state_key: &str, content: &str, now: &str) -> Result<(), MatrixStoreError> {
1241 let value: serde_json::Value = serde_json::from_str(content)?;
1242 let membership_str = value
1243 .get("membership")
1244 .and_then(|v| v.as_str())
1245 .ok_or_else(|| MatrixStoreError::InvalidMembership("missing 'membership' field".to_string()))?;
1246 let membership =
1247 Membership::from_wire_name(membership_str).ok_or_else(|| MatrixStoreError::InvalidMembership(membership_str.to_string()))?;
1248 let user_id = user_id_of(tx, state_key)?.ok_or_else(|| MatrixStoreError::UnknownMxid(state_key.to_string()))?;
1249 tx.execute(
1250 "INSERT INTO room_members (room_id, user_id, membership, power_level, updated_at)
1251 VALUES (?1, ?2, ?3, NULL, ?4)
1252 ON CONFLICT(room_id, user_id) DO UPDATE SET membership = excluded.membership, updated_at = excluded.updated_at",
1253 params![room_id, user_id, membership.as_str(), now],
1254 )?;
1255 Ok(())
1256}
1257
1258fn refresh_power_levels(tx: &Transaction, room_id: &str, content: &str) -> Result<(), MatrixStoreError> {
1267 let value: serde_json::Value = serde_json::from_str(content)?;
1268 tx.execute("UPDATE room_members SET power_level = NULL WHERE room_id = ?1", params![room_id])?;
1269 if let Some(users) = value.get("users").and_then(|v| v.as_object()) {
1270 for (mxid, level) in users {
1271 let Some(level) = level.as_i64() else { continue };
1272 let Some(user_id) = user_id_of(tx, mxid)? else { continue };
1273 tx.execute(
1274 "UPDATE room_members SET power_level = ?1 WHERE room_id = ?2 AND user_id = ?3",
1275 params![level, room_id, user_id],
1276 )?;
1277 }
1278 }
1279 Ok(())
1280}
1281
1282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1294pub enum PowerAction {
1295 Invite,
1296 Kick,
1297 Ban,
1298 Redact,
1299 StateDefault,
1300}
1301
1302impl PowerAction {
1303 fn field_and_default(self) -> (&'static str, i64) {
1304 match self {
1305 PowerAction::Invite => ("invite", 50),
1306 PowerAction::Kick => ("kick", 50),
1307 PowerAction::Ban => ("ban", 50),
1308 PowerAction::Redact => ("redact", 50),
1309 PowerAction::StateDefault => ("state_default", 50),
1310 }
1311 }
1312}
1313
1314pub fn user_level(power_levels: &serde_json::Value, mxid: &str) -> i64 {
1317 power_levels
1318 .get("users")
1319 .and_then(|users| users.get(mxid))
1320 .and_then(serde_json::Value::as_i64)
1321 .unwrap_or_else(|| power_levels.get("users_default").and_then(serde_json::Value::as_i64).unwrap_or(0))
1322}
1323
1324pub fn event_level(power_levels: &serde_json::Value, event_type: &str, is_state: bool) -> i64 {
1329 if let Some(level) = power_levels.get("events").and_then(|events| events.get(event_type)).and_then(serde_json::Value::as_i64) {
1330 return level;
1331 }
1332 let (key, default) = if is_state { ("state_default", 50) } else { ("events_default", 0) };
1333 power_levels.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default)
1334}
1335
1336pub fn can(power_levels: &serde_json::Value, action: PowerAction, mxid: &str) -> bool {
1340 let (field, default) = action.field_and_default();
1341 let required = power_levels.get(field).and_then(serde_json::Value::as_i64).unwrap_or(default);
1342 user_level(power_levels, mxid) >= required
1343}
1344
1345pub fn can_act_on(power_levels: &serde_json::Value, action: PowerAction, sender_mxid: &str, target_mxid: &str, self_leave: bool) -> bool {
1358 if self_leave && sender_mxid == target_mxid {
1359 return true;
1360 }
1361 can(power_levels, action, sender_mxid) && user_level(power_levels, sender_mxid) > user_level(power_levels, target_mxid)
1362}
1363
1364fn power_levels_scalar_default(key: &str) -> i64 {
1369 match key {
1370 "events_default" | "users_default" => 0,
1371 _ => 50,
1372 }
1373}
1374
1375const POWER_LEVELS_SCALAR_KEYS: [&str; 7] = ["ban", "kick", "redact", "invite", "state_default", "events_default", "users_default"];
1380
1381fn reject_if_either_side_exceeds(old_value: Option<i64>, new_value: Option<i64>, sender_level: i64) -> Result<(), &'static str> {
1387 if old_value != new_value && (old_value.is_some_and(|v| v > sender_level) || new_value.is_some_and(|v| v > sender_level)) {
1388 return Err("cannot change a power-level field at or above your own level");
1389 }
1390 Ok(())
1391}
1392
1393pub fn validate_power_levels_change(old: &serde_json::Value, new: &serde_json::Value, sender_mxid: &str) -> Result<(), &'static str> {
1417 let sender_level = user_level(old, sender_mxid);
1418
1419 for key in POWER_LEVELS_SCALAR_KEYS {
1420 let default = power_levels_scalar_default(key);
1421 let old_value = old.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default);
1422 let new_value = new.get(key).and_then(serde_json::Value::as_i64).unwrap_or(default);
1423 reject_if_either_side_exceeds(Some(old_value), Some(new_value), sender_level)?;
1424 }
1425
1426 let old_events = old.get("events").and_then(serde_json::Value::as_object);
1427 let new_events = new.get("events").and_then(serde_json::Value::as_object);
1428 let mut event_type_keys: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
1429 if let Some(map) = old_events {
1430 event_type_keys.extend(map.keys().map(String::as_str));
1431 }
1432 if let Some(map) = new_events {
1433 event_type_keys.extend(map.keys().map(String::as_str));
1434 }
1435 for event_type in event_type_keys {
1436 let old_value = old_events.and_then(|m| m.get(event_type)).and_then(serde_json::Value::as_i64);
1437 let new_value = new_events.and_then(|m| m.get(event_type)).and_then(serde_json::Value::as_i64);
1438 reject_if_either_side_exceeds(old_value, new_value, sender_level)?;
1439 }
1440
1441 let old_notif_room = old.get("notifications").and_then(|v| v.get("room")).and_then(serde_json::Value::as_i64);
1442 let new_notif_room = new.get("notifications").and_then(|v| v.get("room")).and_then(serde_json::Value::as_i64);
1443 reject_if_either_side_exceeds(old_notif_room, new_notif_room, sender_level)?;
1444
1445 let old_users = old.get("users").and_then(serde_json::Value::as_object);
1446 let new_users = new.get("users").and_then(serde_json::Value::as_object);
1447 let mut user_keys: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
1448 if let Some(map) = old_users {
1449 user_keys.extend(map.keys().map(String::as_str));
1450 }
1451 if let Some(map) = new_users {
1452 user_keys.extend(map.keys().map(String::as_str));
1453 }
1454 for target_mxid in user_keys {
1455 let old_effective = user_level(old, target_mxid);
1456 let new_effective = user_level(new, target_mxid);
1457 if old_effective == new_effective {
1458 continue;
1459 }
1460 if target_mxid != sender_mxid && old_effective >= sender_level {
1461 return Err("cannot change the level of a user at or above your own level");
1462 }
1463 if new_effective > sender_level {
1464 return Err("cannot set a user's level above your own");
1465 }
1466 }
1467
1468 Ok(())
1469}
1470
1471pub fn stripped_state_json(conn: &Connection, event: &MatrixEvent) -> Result<serde_json::Value, MatrixStoreError> {
1489 let sender = mxid_of(conn, event.sender_user_id)?.unwrap_or_default();
1490 let content: serde_json::Value = serde_json::from_str(&event.content)?;
1491 Ok(serde_json::json!({
1492 "content": content,
1493 "state_key": event.state_key.clone().unwrap_or_default(),
1494 "type": event.event_type,
1495 "sender": sender,
1496 }))
1497}
1498
1499pub fn stripped_invite_state(conn: &Connection, room_id: &str, inviter_user_id: i64) -> Result<Vec<serde_json::Value>, MatrixStoreError> {
1510 let mut out = Vec::new();
1511 for event_type in ["m.room.create", "m.room.join_rules", "m.room.encryption", "m.room.name"] {
1512 if let Some(event) = current_state_event(conn, room_id, event_type, "")? {
1513 out.push(stripped_state_json(conn, &event)?);
1514 }
1515 }
1516 if let Some(inviter_mxid) = mxid_of(conn, inviter_user_id)? {
1517 if let Some(event) = current_state_event(conn, room_id, "m.room.member", &inviter_mxid)? {
1518 out.push(stripped_state_json(conn, &event)?);
1519 }
1520 }
1521 Ok(out)
1522}
1523
1524pub fn get_event(conn: &Connection, event_id: &str) -> rusqlite::Result<Option<MatrixEvent>> {
1525 let closed = conn
1526 .query_row(&format!("SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE event_id = ?1"), params![event_id], event_from_row)
1527 .optional()?;
1528 if closed.is_some() {
1529 return Ok(closed);
1530 }
1531 match crate::public_channels::get_event(conn, event_id) {
1533 Ok(found) => Ok(found),
1534 Err(rusqlite::Error::SqliteFailure(_, Some(msg))) if msg.contains("no such table") => Ok(None),
1535 Err(e) => Err(e),
1536 }
1537}
1538
1539pub fn current_state_event(conn: &Connection, room_id: &str, event_type: &str, state_key: &str) -> rusqlite::Result<Option<MatrixEvent>> {
1540 conn.query_row(
1541 &format!(
1542 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM current_state cs JOIN events e ON e.event_id = cs.event_id
1543 WHERE cs.room_id = ?1 AND cs.event_type = ?2 AND cs.state_key = ?3"
1544 ),
1545 params![room_id, event_type, state_key],
1546 event_from_row,
1547 )
1548 .optional()
1549}
1550
1551pub fn current_state_all(conn: &Connection, room_id: &str) -> rusqlite::Result<Vec<MatrixEvent>> {
1552 let mut stmt = conn.prepare(&format!(
1553 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM current_state cs JOIN events e ON e.event_id = cs.event_id WHERE cs.room_id = ?1"
1554 ))?;
1555 let mut rows = stmt.query(params![room_id])?;
1556 collect_events(&mut rows)
1557}
1558
1559pub fn state_events_of_type_at(conn: &Connection, room_id: &str, event_type: &str, at_stream_id: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1567 let mut stmt = conn.prepare(&format!(
1568 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1569 WHERE e.room_id = ?1 AND e.event_type = ?2 AND e.state_key IS NOT NULL AND e.stream_id <= ?3
1570 AND e.stream_id = (
1571 SELECT MAX(stream_id) FROM events e2
1572 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
1573 )"
1574 ))?;
1575 let mut rows = stmt.query(params![room_id, event_type, at_stream_id])?;
1576 collect_events(&mut rows)
1577}
1578
1579pub fn member_state_changed_in_window(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1589 let mut stmt = conn.prepare(&format!(
1590 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1591 WHERE e.room_id = ?1 AND e.event_type = 'm.room.member' AND e.state_key IS NOT NULL
1592 AND e.stream_id = (
1593 SELECT MAX(e2.stream_id) FROM events e2
1594 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
1595 )
1596 AND e.stream_id > ?2"
1597 ))?;
1598 let mut rows = stmt.query(params![room_id, since_exclusive, upto_inclusive])?;
1599 collect_events(&mut rows)
1600}
1601
1602pub fn non_member_state_changed_in_window(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1611 let mut stmt = conn.prepare(&format!(
1612 "SELECT {EVENT_SELECT_COLUMNS_ALIASED} FROM events e
1613 WHERE e.room_id = ?1 AND e.event_type != 'm.room.member' AND e.state_key IS NOT NULL
1614 AND e.stream_id = (
1615 SELECT MAX(e2.stream_id) FROM events e2
1616 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
1617 )
1618 AND e.stream_id > ?2"
1619 ))?;
1620 let mut rows = stmt.query(params![room_id, since_exclusive, upto_inclusive])?;
1621 collect_events(&mut rows)
1622}
1623
1624pub fn events_in_room_after(conn: &Connection, room_id: &str, since_stream: i64, limit: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1627 let mut stmt = conn.prepare(&format!(
1628 "SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE room_id = ?1 AND stream_id > ?2 ORDER BY stream_id ASC LIMIT ?3"
1629 ))?;
1630 let mut rows = stmt.query(params![room_id, since_stream, limit])?;
1631 let mut out = collect_events(&mut rows)?;
1632 if crate::public_channels::is_public_room(conn, room_id)? {
1633 out.extend(crate::public_channels::events_after(conn, room_id, since_stream, limit)?);
1634 out.sort_by_key(|e| e.stream_id);
1635 out.truncate(limit.max(0) as usize);
1636 }
1637 Ok(out)
1638}
1639
1640pub fn events_in_room_before(conn: &Connection, room_id: &str, before_stream: i64, limit: i64) -> rusqlite::Result<Vec<MatrixEvent>> {
1644 let mut stmt = conn.prepare(&format!(
1645 "SELECT {EVENT_SELECT_COLUMNS} FROM events WHERE room_id = ?1 AND stream_id < ?2 ORDER BY stream_id DESC LIMIT ?3"
1646 ))?;
1647 let mut rows = stmt.query(params![room_id, before_stream, limit])?;
1648 let mut out = collect_events(&mut rows)?;
1649 if crate::public_channels::is_public_room(conn, room_id)? {
1650 out.extend(crate::public_channels::events_before(conn, room_id, before_stream, limit)?);
1651 out.sort_by_key(|e| std::cmp::Reverse(e.stream_id));
1652 out.truncate(limit.max(0) as usize);
1653 }
1654 Ok(out)
1655}
1656
1657fn populate_relations(tx: &Transaction, event_id: &str, room_id: &str, sender_user_id: i64, content: &str) -> Result<(), MatrixStoreError> {
1681 let Ok(value) = serde_json::from_str::<serde_json::Value>(content) else {
1682 return Ok(());
1683 };
1684 let Some(relates_to) = value.get("m.relates_to") else {
1685 return Ok(());
1686 };
1687
1688 let (rel_type, target_id, agg_key): (String, String, Option<String>) = if let Some(reply) = relates_to.get("m.in_reply_to") {
1689 match reply.get("event_id").and_then(|v| v.as_str()) {
1690 Some(target) => ("m.in_reply_to".to_string(), target.to_string(), None),
1691 None => return Ok(()),
1692 }
1693 } else {
1694 let rel_type = relates_to.get("rel_type").and_then(|v| v.as_str());
1695 let target = relates_to.get("event_id").and_then(|v| v.as_str());
1696 match (rel_type, target) {
1697 (Some(rt), Some(target)) => {
1698 let key = relates_to.get("key").and_then(|v| v.as_str()).map(str::to_string);
1699 (rt.to_string(), target.to_string(), key)
1700 }
1701 _ => return Ok(()),
1702 }
1703 };
1704
1705 let target_room: Option<String> = tx
1706 .query_row("SELECT room_id FROM events WHERE event_id = ?1", params![target_id], |row| row.get(0))
1707 .optional()?;
1708 match target_room {
1709 Some(ref found_room) if found_room == room_id => {}
1710 _ => return Err(MatrixStoreError::InvalidRelationTarget(target_id)),
1711 }
1712
1713 if rel_type == "m.annotation" {
1714 let duplicate: Option<i64> = tx
1715 .query_row(
1716 "SELECT 1 FROM relations r JOIN events e ON e.event_id = r.event_id
1717 WHERE r.target_id = ?1 AND r.rel_type = 'm.annotation' AND r.agg_key IS ?2 AND e.sender_user_id = ?3
1718 AND e.redacted_by IS NULL
1719 LIMIT 1",
1720 params![target_id, agg_key, sender_user_id],
1721 |row| row.get(0),
1722 )
1723 .optional()?;
1724 if duplicate.is_some() {
1725 return Err(MatrixStoreError::DuplicateAnnotation);
1726 }
1727 }
1728
1729 tx.execute(
1730 "INSERT INTO relations (event_id, room_id, rel_type, target_id, agg_key) VALUES (?1, ?2, ?3, ?4, ?5)",
1731 params![event_id, room_id, rel_type, target_id, agg_key],
1732 )?;
1733 Ok(())
1734}
1735
1736pub fn relations_of(
1742 conn: &Connection,
1743 target_event_id: &str,
1744 rel_type: Option<&str>,
1745 event_type: Option<&str>,
1746 before_stream: i64,
1747 limit: i64,
1748) -> rusqlite::Result<Vec<MatrixEvent>> {
1749 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 < ?");
1754 let mut values: Vec<&dyn rusqlite::ToSql> = vec![&target_event_id, &before_stream];
1755 if let Some(rt) = &rel_type {
1756 sql.push_str(" AND r.rel_type = ?");
1757 values.push(rt);
1758 }
1759 if let Some(et) = &event_type {
1760 sql.push_str(" AND e.event_type = ?");
1761 values.push(et);
1762 }
1763 sql.push_str(" ORDER BY e.stream_id DESC LIMIT ?");
1764 values.push(&limit);
1765
1766 let mut stmt = conn.prepare(&sql)?;
1767 let mut rows = stmt.query(values.as_slice())?;
1768 collect_events(&mut rows)
1769}
1770
1771fn redact_content_per_v11(event_type: &str, content: &str) -> Result<String, MatrixStoreError> {
1787 let value: serde_json::Value = serde_json::from_str(content)?;
1788 let obj = value.as_object().cloned().unwrap_or_default();
1789 let mut kept = serde_json::Map::new();
1790
1791 match event_type {
1792 "m.room.create" => kept = obj,
1793 "m.room.member" => {
1794 for key in ["membership", "join_authorised_via_users_server"] {
1795 if let Some(v) = obj.get(key) {
1796 kept.insert(key.to_string(), v.clone());
1797 }
1798 }
1799 if let Some(signed) = obj.get("third_party_invite").and_then(|v| v.get("signed")) {
1800 let mut third_party_invite = serde_json::Map::new();
1801 third_party_invite.insert("signed".to_string(), signed.clone());
1802 kept.insert("third_party_invite".to_string(), serde_json::Value::Object(third_party_invite));
1803 }
1804 }
1805 "m.room.join_rules" => {
1806 for key in ["join_rule", "allow"] {
1807 if let Some(v) = obj.get(key) {
1808 kept.insert(key.to_string(), v.clone());
1809 }
1810 }
1811 }
1812 "m.room.power_levels" => {
1813 for key in [
1814 "ban",
1815 "events",
1816 "events_default",
1817 "invite",
1818 "kick",
1819 "redact",
1820 "state_default",
1821 "users",
1822 "users_default",
1823 ] {
1824 if let Some(v) = obj.get(key) {
1825 kept.insert(key.to_string(), v.clone());
1826 }
1827 }
1828 }
1829 "m.room.history_visibility" => {
1830 if let Some(v) = obj.get("history_visibility") {
1831 kept.insert("history_visibility".to_string(), v.clone());
1832 }
1833 }
1834 _ => {}
1835 }
1836
1837 Ok(serde_json::Value::Object(kept).to_string())
1838}
1839
1840fn redact_event_in_tx(
1861 tx: &Transaction,
1862 room_id: &str,
1863 target_event_id: &str,
1864 redaction_event_id: &str,
1865 sender_user_id: i64,
1866 reason: Option<&str>,
1867 origin_server_ts: i64,
1868) -> Result<MatrixEvent, MatrixStoreError> {
1869 let target: Option<(String, String, String)> = tx
1870 .query_row(
1871 "SELECT event_type, content, room_id FROM events WHERE event_id = ?1",
1872 params![target_event_id],
1873 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
1874 )
1875 .optional()?;
1876 let (target_type, target_content, target_room) = target.ok_or_else(|| MatrixStoreError::UnknownEventId(target_event_id.to_string()))?;
1877 if target_room != room_id {
1878 return Err(MatrixStoreError::WrongRoom(target_event_id.to_string()));
1879 }
1880 if target_type == "m.room.create" || target_type == "m.room.encryption" {
1881 return Err(MatrixStoreError::UnredactableEvent(target_type));
1882 }
1883
1884 let stream_id = next_stream_id(tx)?;
1885 let mut redaction_content = serde_json::json!({ "redacts": target_event_id });
1888 if let Some(r) = reason {
1889 redaction_content["reason"] = serde_json::Value::String(r.to_string());
1890 }
1891 let redaction_content = redaction_content.to_string();
1892 tx.execute(
1893 "INSERT INTO events (stream_id, event_id, room_id, sender_user_id, event_type, state_key, content, origin_server_ts, txn_id, redacts)
1894 VALUES (?1, ?2, ?3, ?4, 'm.room.redaction', NULL, ?5, ?6, NULL, ?7)",
1895 params![stream_id, redaction_event_id, room_id, sender_user_id, redaction_content, origin_server_ts, target_event_id],
1896 )?;
1897
1898 let stripped_content = redact_content_per_v11(&target_type, &target_content)?;
1899 tx.execute(
1900 "UPDATE events SET content = ?1, redacted_by = ?2 WHERE event_id = ?3",
1901 params![stripped_content, redaction_event_id, target_event_id],
1902 )?;
1903
1904 Ok(MatrixEvent {
1905 stream_id,
1906 event_id: redaction_event_id.to_string(),
1907 room_id: room_id.to_string(),
1908 sender_user_id,
1909 event_type: "m.room.redaction".to_string(),
1910 state_key: None,
1911 content: redaction_content,
1912 origin_server_ts,
1913 txn_id: None,
1914 redacts: Some(target_event_id.to_string()),
1915 redacted_by: None,
1916 })
1917}
1918
1919pub fn redact_event(
1920 conn: &mut Connection,
1921 room_id: &str,
1922 target_event_id: &str,
1923 redaction_event_id: &str,
1924 sender_user_id: i64,
1925 reason: Option<&str>,
1926 origin_server_ts: i64,
1927) -> Result<MatrixEvent, MatrixStoreError> {
1928 let tx = conn.transaction()?;
1929 let event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
1930 tx.commit()?;
1931 Ok(event)
1932}
1933
1934#[derive(Debug, Clone, Copy)]
1937pub struct Redaction<'a> {
1938 pub room_id: &'a str,
1939 pub target_event_id: &'a str,
1940 pub redaction_event_id: &'a str,
1941 pub sender_user_id: i64,
1942 pub reason: Option<&'a str>,
1943 pub origin_server_ts: i64,
1944}
1945
1946pub fn redact_event_marked(
1956 conn: &mut Connection,
1957 redaction: &Redaction<'_>,
1958 extra_content: &serde_json::Value,
1959) -> Result<MatrixEvent, MatrixStoreError> {
1960 let Redaction { room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts } = *redaction;
1961 let tx = conn.transaction()?;
1962 let mut event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
1963 let mut content: serde_json::Value = serde_json::from_str(&event.content)?;
1964 if let (Some(content_obj), Some(extra_obj)) = (content.as_object_mut(), extra_content.as_object()) {
1965 for (key, value) in extra_obj {
1966 content_obj.insert(key.clone(), value.clone());
1967 }
1968 }
1969 let content_str = content.to_string();
1970 tx.execute("UPDATE events SET content = ?1 WHERE event_id = ?2", params![content_str, redaction_event_id])?;
1971 event.content = content_str;
1972 tx.commit()?;
1973 Ok(event)
1974}
1975
1976#[allow(clippy::too_many_arguments)]
1981pub fn redact_event_deduped(
1982 conn: &mut Connection,
1983 device_id: &str,
1984 txn_id: &str,
1985 room_id: &str,
1986 target_event_id: &str,
1987 redaction_event_id: &str,
1988 sender_user_id: i64,
1989 reason: Option<&str>,
1990 origin_server_ts: i64,
1991 now: &str,
1992) -> Result<DedupedWrite, MatrixStoreError> {
1993 let tx = conn.transaction()?;
1994 if let TxnDedupEntry::Seen(existing_event_id) = txn_dedup_lookup(&tx, sender_user_id, device_id, txn_id)? {
1995 let existing_event_id = existing_event_id.ok_or_else(|| MatrixStoreError::UnknownEventId(txn_id.to_string()))?;
1996 let event = get_event(&tx, &existing_event_id)?.ok_or_else(|| MatrixStoreError::UnknownEventId(existing_event_id.clone()))?;
1997 tx.commit()?;
1998 return Ok(DedupedWrite::Existing(event));
1999 }
2000 let event = redact_event_in_tx(&tx, room_id, target_event_id, redaction_event_id, sender_user_id, reason, origin_server_ts)?;
2001 txn_dedup_record(&tx, sender_user_id, device_id, txn_id, Some(redaction_event_id), now)?;
2002 tx.commit()?;
2003 Ok(DedupedWrite::New(event))
2004}
2005
2006#[derive(Debug, Clone, PartialEq)]
2011pub struct RoomMember {
2012 pub room_id: String,
2013 pub user_id: i64,
2014 pub membership: Membership,
2015 pub power_level: Option<i64>,
2016 pub updated_at: String,
2017}
2018
2019fn room_member_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<RoomMember> {
2020 let membership_raw: String = row.get(2)?;
2021 Ok(RoomMember {
2022 room_id: row.get(0)?,
2023 user_id: row.get(1)?,
2024 membership: decode_enum(2, "membership", &membership_raw, Membership::from_wire_name)?,
2025 power_level: row.get(3)?,
2026 updated_at: row.get(4)?,
2027 })
2028}
2029
2030const ROOM_MEMBER_SELECT_COLUMNS: &str = "room_id, user_id, membership, power_level, updated_at";
2031
2032pub fn room_members(conn: &Connection, room_id: &str, membership: Option<Membership>) -> rusqlite::Result<Vec<RoomMember>> {
2034 match membership {
2035 Some(m) => {
2036 let mut stmt = conn.prepare(&format!(
2037 "SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1 AND membership = ?2"
2038 ))?;
2039 let rows = stmt.query_map(params![room_id, m.as_str()], room_member_from_row)?;
2040 rows.collect()
2041 }
2042 None => {
2043 let mut stmt = conn.prepare(&format!("SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1"))?;
2044 let rows = stmt.query_map(params![room_id], room_member_from_row)?;
2045 rows.collect()
2046 }
2047 }
2048}
2049
2050pub fn room_member(conn: &Connection, room_id: &str, user_id: i64) -> rusqlite::Result<Option<RoomMember>> {
2055 conn.query_row(
2056 &format!("SELECT {ROOM_MEMBER_SELECT_COLUMNS} FROM room_members WHERE room_id = ?1 AND user_id = ?2"),
2057 params![room_id, user_id],
2058 room_member_from_row,
2059 )
2060 .optional()
2061}
2062
2063pub fn forget_membership(conn: &Connection, room_id: &str, user_id: i64) -> rusqlite::Result<usize> {
2070 conn.execute(
2071 "DELETE FROM room_members WHERE room_id = ?1 AND user_id = ?2 AND membership = 'leave'",
2072 params![room_id, user_id],
2073 )
2074}
2075
2076pub fn room_heroes(conn: &Connection, room_id: &str, exclude_user_id: i64, limit: i64) -> rusqlite::Result<Vec<i64>> {
2080 let mut stmt = conn.prepare(
2081 "SELECT user_id FROM room_members
2082 WHERE room_id = ?1 AND user_id != ?2 AND membership IN ('join', 'invite')
2083 ORDER BY updated_at ASC LIMIT ?3",
2084 )?;
2085 let rows = stmt.query_map(params![room_id, exclude_user_id, limit], |row| row.get(0))?;
2086 rows.collect()
2087}
2088
2089pub fn rooms_for_user(conn: &Connection, user_id: i64, membership: Option<Membership>) -> rusqlite::Result<Vec<String>> {
2093 match membership {
2094 Some(m) => {
2095 let mut stmt = conn.prepare("SELECT room_id FROM room_members WHERE user_id = ?1 AND membership = ?2")?;
2096 let rows = stmt.query_map(params![user_id, m.as_str()], |row| row.get(0))?;
2097 rows.collect()
2098 }
2099 None => {
2100 let mut stmt = conn.prepare("SELECT room_id FROM room_members WHERE user_id = ?1")?;
2101 let rows = stmt.query_map(params![user_id], |row| row.get(0))?;
2102 rows.collect()
2103 }
2104 }
2105}
2106
2107pub fn rooms_changed_in_window(
2122 conn: &Connection,
2123 room_ids: &[String],
2124 caller_user_id: i64,
2125 since_exclusive: i64,
2126 upto_inclusive: i64,
2127) -> rusqlite::Result<HashSet<String>> {
2128 let mut changed = HashSet::new();
2129 if room_ids.is_empty() {
2130 return Ok(changed);
2131 }
2132 let placeholders = vec!["?"; room_ids.len()].join(",");
2133
2134 for table in ["events", "receipts", "pub_events"] {
2138 let sql = format!("SELECT DISTINCT room_id FROM {table} WHERE stream_id > ? AND stream_id <= ? AND room_id IN ({placeholders})");
2139 let mut stmt = conn.prepare(&sql)?;
2140 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&since_exclusive, &upto_inclusive];
2141 for room_id in room_ids {
2142 bound.push(room_id as &dyn rusqlite::ToSql);
2143 }
2144 let mut rows = stmt.query(bound.as_slice())?;
2145 while let Some(row) = rows.next()? {
2146 changed.insert(row.get::<_, String>(0)?);
2147 }
2148 }
2149
2150 let room_id_set: HashSet<&str> = room_ids.iter().map(String::as_str).collect();
2156 let mut stmt = conn.prepare("SELECT DISTINCT room_id FROM account_data WHERE user_id = ?1 AND stream_id > ?2 AND stream_id <= ?3")?;
2157 let mut rows = stmt.query(params![caller_user_id, since_exclusive, upto_inclusive])?;
2158 while let Some(row) = rows.next()? {
2159 let room_id: String = row.get(0)?;
2160 if room_id_set.contains(room_id.as_str()) {
2161 changed.insert(room_id);
2162 }
2163 }
2164
2165 Ok(changed)
2166}
2167
2168pub fn membership_at(conn: &Connection, room_id: &str, mxid: &str, at_stream_id: i64) -> rusqlite::Result<Option<Membership>> {
2175 let content: Option<String> = conn
2176 .query_row(
2177 "SELECT content FROM events
2178 WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key = ?2 AND stream_id <= ?3
2179 ORDER BY stream_id DESC LIMIT 1",
2180 params![room_id, mxid, at_stream_id],
2181 |row| row.get(0),
2182 )
2183 .optional()?;
2184 Ok(content
2185 .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
2186 .and_then(|value| value.get("membership").and_then(|m| m.as_str()).and_then(Membership::from_wire_name)))
2187}
2188
2189pub fn rooms_with_member_events_in_window(conn: &Connection, room_ids: &[String], from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<Vec<String>> {
2196 if room_ids.is_empty() {
2197 return Ok(Vec::new());
2198 }
2199 let placeholders = vec!["?"; room_ids.len()].join(",");
2200 let sql = format!(
2201 "SELECT DISTINCT room_id FROM events
2202 WHERE event_type = 'm.room.member' AND stream_id > ? AND stream_id <= ? AND room_id IN ({placeholders})"
2203 );
2204 let mut stmt = conn.prepare(&sql)?;
2205 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&from_exclusive, &to_inclusive];
2206 for room_id in room_ids {
2207 bound.push(room_id as &dyn rusqlite::ToSql);
2208 }
2209 let rows = stmt.query_map(bound.as_slice(), |row| row.get(0))?;
2210 rows.collect()
2211}
2212
2213pub fn member_state_keys_in_window(conn: &Connection, room_id: &str, from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<Vec<String>> {
2218 let mut stmt = conn.prepare(
2219 "SELECT DISTINCT state_key FROM events
2220 WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key IS NOT NULL AND stream_id > ?2 AND stream_id <= ?3",
2221 )?;
2222 let rows = stmt.query_map(params![room_id, from_exclusive, to_inclusive], |row| row.get(0))?;
2223 rows.collect()
2224}
2225
2226pub fn user_ids_with_leave_transition_in_rooms(
2238 conn: &Connection,
2239 room_ids: &[String],
2240 from_exclusive: i64,
2241 to_inclusive: i64,
2242) -> rusqlite::Result<Vec<i64>> {
2243 if room_ids.is_empty() {
2244 return Ok(Vec::new());
2245 }
2246 let placeholders = vec!["?"; room_ids.len()].join(",");
2247 let sql = format!(
2248 "SELECT DISTINCT e.state_key, e.content FROM events e
2249 WHERE e.event_type = 'm.room.member' AND e.state_key IS NOT NULL
2250 AND e.stream_id > ? AND e.stream_id <= ?
2251 AND e.room_id IN ({placeholders})"
2252 );
2253 let mut stmt = conn.prepare(&sql)?;
2254 let mut bound: Vec<&dyn rusqlite::ToSql> = vec![&from_exclusive, &to_inclusive];
2255 for room_id in room_ids {
2256 bound.push(room_id as &dyn rusqlite::ToSql);
2257 }
2258 let mut rows = stmt.query(bound.as_slice())?;
2259 let mut mxids = Vec::new();
2260 while let Some(row) = rows.next()? {
2261 let mxid: String = row.get(0)?;
2262 let content: String = row.get(1)?;
2263 if let Ok(value) = serde_json::from_str::<serde_json::Value>(&content) {
2264 if matches!(value.get("membership").and_then(|m| m.as_str()), Some("leave") | Some("ban")) {
2265 mxids.push(mxid);
2266 }
2267 }
2268 }
2269 let mut user_ids = Vec::new();
2270 for mxid in mxids {
2271 if let Some(user_id) = user_id_of(conn, &mxid)? {
2272 user_ids.push(user_id);
2273 }
2274 }
2275 Ok(user_ids)
2276}
2277
2278pub fn private_room_messages_sent_since(conn: &Connection, sender_user_id: i64, since_ms: i64) -> rusqlite::Result<i64> {
2289 conn.query_row(
2290 "SELECT COUNT(*) FROM events e JOIN rooms r ON r.id = e.room_id
2291 WHERE e.sender_user_id = ?1 AND e.state_key IS NULL AND e.event_type != 'm.room.redaction'
2292 AND e.origin_server_ts >= ?2 AND r.join_rule = 'invite'",
2293 params![sender_user_id, since_ms],
2294 |row| row.get(0),
2295 )
2296}
2297
2298pub fn txn_dedup_count_since(conn: &Connection, user_id: i64, device_id: &str, since: &str) -> rusqlite::Result<i64> {
2309 conn.query_row(
2310 "SELECT COUNT(*) FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND created_at >= ?3",
2311 params![user_id, device_id, since],
2312 |row| row.get(0),
2313 )
2314}
2315
2316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2326pub enum HistoryWindow {
2327 Nothing,
2329 All,
2331 UpTo(i64),
2333}
2334
2335impl HistoryWindow {
2336 pub fn contains(self, stream_id: i64) -> bool {
2337 match self {
2338 HistoryWindow::Nothing => false,
2339 HistoryWindow::All => true,
2340 HistoryWindow::UpTo(upto) => stream_id <= upto,
2341 }
2342 }
2343}
2344
2345pub fn visible_upper_bound(conn: &Connection, room: &Room, caller_user_id: i64) -> Result<HistoryWindow, MatrixStoreError> {
2365 if room.history_visibility == HistoryVisibility::WorldReadable {
2366 return Ok(HistoryWindow::All);
2367 }
2368 let Some(mxid) = mxid_of(conn, caller_user_id)? else {
2369 return Ok(HistoryWindow::Nothing);
2370 };
2371 let Some(member_event) = current_state_event(conn, &room.id, "m.room.member", &mxid)? else {
2372 return Ok(HistoryWindow::Nothing);
2373 };
2374 let content: serde_json::Value = serde_json::from_str(&member_event.content)?;
2375 match content.get("membership").and_then(|v| v.as_str()) {
2376 Some("join") => Ok(HistoryWindow::All),
2377 Some("leave") | Some("ban") => Ok(HistoryWindow::UpTo(member_event.stream_id)),
2378 _ => Ok(HistoryWindow::Nothing),
2379 }
2380}
2381
2382#[derive(Debug, Clone, PartialEq)]
2387pub struct ReceiptRow {
2388 pub room_id: String,
2389 pub user_id: i64,
2390 pub receipt_type: ReceiptType,
2391 pub event_id: String,
2392 pub ts: i64,
2393 pub stream_id: i64,
2394}
2395
2396fn receipt_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<ReceiptRow> {
2397 let receipt_type_raw: String = row.get(2)?;
2398 Ok(ReceiptRow {
2399 room_id: row.get(0)?,
2400 user_id: row.get(1)?,
2401 receipt_type: decode_enum(2, "receipt_type", &receipt_type_raw, ReceiptType::from_wire_name)?,
2402 event_id: row.get(3)?,
2403 ts: row.get(4)?,
2404 stream_id: row.get(5)?,
2405 })
2406}
2407
2408const RECEIPT_SELECT_COLUMNS: &str = "room_id, user_id, receipt_type, event_id, ts, stream_id";
2409
2410pub fn get_receipt(conn: &Connection, room_id: &str, user_id: i64, receipt_type: ReceiptType) -> rusqlite::Result<Option<ReceiptRow>> {
2411 conn.query_row(
2412 &format!("SELECT {RECEIPT_SELECT_COLUMNS} FROM receipts WHERE room_id = ?1 AND user_id = ?2 AND receipt_type = ?3"),
2413 params![room_id, user_id, receipt_type.as_str()],
2414 receipt_from_row,
2415 )
2416 .optional()
2417}
2418
2419pub fn receipts_changed_in_room(conn: &Connection, room_id: &str, since_exclusive: i64, upto_inclusive: i64) -> rusqlite::Result<Vec<ReceiptRow>> {
2425 let mut stmt = conn.prepare(&format!(
2426 "SELECT {RECEIPT_SELECT_COLUMNS} FROM receipts WHERE room_id = ?1 AND stream_id > ?2 AND stream_id <= ?3"
2427 ))?;
2428 let rows = stmt.query_map(params![room_id, since_exclusive, upto_inclusive], receipt_from_row)?;
2429 rows.collect()
2430}
2431
2432pub fn upsert_receipt(
2444 conn: &mut Connection,
2445 room_id: &str,
2446 user_id: i64,
2447 receipt_type: ReceiptType,
2448 event_id: &str,
2449 ts: i64,
2450) -> Result<i64, MatrixStoreError> {
2451 let tx = conn.transaction()?;
2452 let stream_id = upsert_receipt_in_tx(&tx, room_id, user_id, receipt_type, event_id, ts)?;
2453 tx.commit()?;
2454 Ok(stream_id)
2455}
2456
2457fn upsert_receipt_in_tx(
2466 tx: &Transaction,
2467 room_id: &str,
2468 user_id: i64,
2469 receipt_type: ReceiptType,
2470 event_id: &str,
2471 ts: i64,
2472) -> Result<i64, MatrixStoreError> {
2473 let target: Option<(i64, String)> = tx
2474 .query_row("SELECT stream_id, room_id FROM events WHERE event_id = ?1", params![event_id], |row| {
2475 Ok((row.get(0)?, row.get(1)?))
2476 })
2477 .optional()?;
2478 let Some((target_position, target_room)) = target else {
2479 if let Some(public) = crate::public_channels::get_event(tx, event_id)? {
2482 if public.room_id != room_id {
2483 return Err(MatrixStoreError::WrongRoom(event_id.to_string()));
2484 }
2485 return Ok(public.stream_id);
2486 }
2487 return Err(MatrixStoreError::UnknownEventId(event_id.to_string()));
2488 };
2489 if target_room != room_id {
2490 return Err(MatrixStoreError::WrongRoom(event_id.to_string()));
2491 }
2492
2493 let existing: Option<(String, i64)> = tx
2494 .query_row(
2495 "SELECT event_id, stream_id FROM receipts WHERE room_id = ?1 AND user_id = ?2 AND receipt_type = ?3",
2496 params![room_id, user_id, receipt_type.as_str()],
2497 |row| Ok((row.get(0)?, row.get(1)?)),
2498 )
2499 .optional()?;
2500
2501 if let Some((existing_event_id, existing_stream_id)) = &existing {
2502 let existing_position: i64 =
2503 tx.query_row("SELECT stream_id FROM events WHERE event_id = ?1", params![existing_event_id], |row| row.get(0))?;
2504 if target_position <= existing_position {
2505 return Ok(*existing_stream_id);
2506 }
2507 }
2508
2509 let stream_id = next_stream_id(tx)?;
2510 tx.execute(
2511 "INSERT INTO receipts (room_id, user_id, receipt_type, event_id, ts, stream_id)
2512 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
2513 ON CONFLICT(room_id, user_id, receipt_type) DO UPDATE SET
2514 event_id = excluded.event_id, ts = excluded.ts, stream_id = excluded.stream_id",
2515 params![room_id, user_id, receipt_type.as_str(), event_id, ts, stream_id],
2516 )?;
2517 Ok(stream_id)
2518}
2519
2520const NOTIFICATION_MESSAGE_TYPES_SQL: &str = "('m.room.message', 'm.room.encrypted', 'org.example.legacy_dm')";
2532
2533pub fn notification_count(conn: &Connection, room: &Room, user_id: i64) -> Result<i64, MatrixStoreError> {
2552 if room.kind == RoomKind::Channel && !room.is_encrypted {
2553 return Ok(0); }
2555 let upper_bound = match visible_upper_bound(conn, room, user_id)? {
2556 HistoryWindow::Nothing => return Ok(0),
2557 HistoryWindow::All => i64::MAX,
2558 HistoryWindow::UpTo(upper) => upper,
2559 };
2560
2561 let mut after_stream_id: i64 = 0;
2562 for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
2563 if let Some(receipt) = get_receipt(conn, &room.id, user_id, receipt_type)? {
2564 if let Some(target) = get_event(conn, &receipt.event_id)? {
2565 after_stream_id = after_stream_id.max(target.stream_id);
2566 }
2567 }
2568 }
2569
2570 let count = conn.query_row(
2571 &format!(
2572 "SELECT COUNT(*) FROM events
2573 WHERE room_id = ?1 AND state_key IS NULL AND sender_user_id != ?2
2574 AND stream_id > ?3 AND stream_id <= ?4
2575 AND event_type IN {NOTIFICATION_MESSAGE_TYPES_SQL}"
2576 ),
2577 params![room.id, user_id, after_stream_id, upper_bound],
2578 |row| row.get(0),
2579 )?;
2580 Ok(count)
2581}
2582
2583pub const GLOBAL_ACCOUNT_DATA_ROOM: &str = "";
2590
2591#[derive(Debug, Clone, PartialEq)]
2592pub struct AccountDataRow {
2593 pub user_id: i64,
2594 pub room_id: String,
2595 pub data_type: String,
2596 pub content: String,
2597 pub stream_id: i64,
2598}
2599
2600fn account_data_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<AccountDataRow> {
2601 Ok(AccountDataRow {
2602 user_id: row.get(0)?,
2603 room_id: row.get(1)?,
2604 data_type: row.get(2)?,
2605 content: row.get(3)?,
2606 stream_id: row.get(4)?,
2607 })
2608}
2609
2610const ACCOUNT_DATA_SELECT_COLUMNS: &str = "user_id, room_id, data_type, content, stream_id";
2611
2612pub fn upsert_account_data(conn: &mut Connection, user_id: i64, room_id: &str, data_type: &str, content: &str) -> Result<i64, MatrixStoreError> {
2616 let tx = conn.transaction()?;
2617 let stream_id = next_stream_id(&tx)?;
2618 tx.execute(
2619 "INSERT INTO account_data (user_id, room_id, data_type, content, stream_id) VALUES (?1, ?2, ?3, ?4, ?5)
2620 ON CONFLICT(user_id, room_id, data_type) DO UPDATE SET content = excluded.content, stream_id = excluded.stream_id",
2621 params![user_id, room_id, data_type, content, stream_id],
2622 )?;
2623 tx.commit()?;
2624 Ok(stream_id)
2625}
2626
2627pub fn get_account_data(conn: &Connection, user_id: i64, room_id: &str, data_type: &str) -> rusqlite::Result<Option<AccountDataRow>> {
2628 conn.query_row(
2629 &format!("SELECT {ACCOUNT_DATA_SELECT_COLUMNS} FROM account_data WHERE user_id = ?1 AND room_id = ?2 AND data_type = ?3"),
2630 params![user_id, room_id, data_type],
2631 account_data_from_row,
2632 )
2633 .optional()
2634}
2635
2636pub fn account_data_since(conn: &Connection, user_id: i64, room_id: &str, since_stream: i64) -> rusqlite::Result<Vec<AccountDataRow>> {
2641 let mut stmt = conn.prepare(&format!(
2642 "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"
2643 ))?;
2644 let rows = stmt.query_map(params![user_id, room_id, since_stream], account_data_from_row)?;
2645 rows.collect()
2646}
2647
2648#[derive(Debug, Clone, PartialEq, Eq)]
2654pub enum TxnDedupEntry {
2655 NotSeen,
2658 Seen(Option<String>),
2661}
2662
2663pub fn txn_dedup_lookup(conn: &Connection, user_id: i64, device_id: &str, txn_id: &str) -> rusqlite::Result<TxnDedupEntry> {
2666 let found: Option<Option<String>> = conn
2667 .query_row(
2668 "SELECT event_id FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND txn_id = ?3",
2669 params![user_id, device_id, txn_id],
2670 |row| row.get(0),
2671 )
2672 .optional()?;
2673 Ok(match found {
2674 None => TxnDedupEntry::NotSeen,
2675 Some(event_id) => TxnDedupEntry::Seen(event_id),
2676 })
2677}
2678
2679pub fn txn_dedup_record(conn: &Connection, user_id: i64, device_id: &str, txn_id: &str, event_id: Option<&str>, now: &str) -> rusqlite::Result<()> {
2684 conn.execute(
2685 "INSERT INTO txn_dedup (user_id, device_id, txn_id, event_id, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
2686 params![user_id, device_id, txn_id, event_id, now],
2687 )?;
2688 Ok(())
2689}
2690
2691pub fn txn_id_for_event(conn: &Connection, user_id: i64, device_id: &str, event_id: &str) -> rusqlite::Result<Option<String>> {
2699 conn.query_row(
2700 "SELECT txn_id FROM txn_dedup WHERE user_id = ?1 AND device_id = ?2 AND event_id = ?3",
2701 params![user_id, device_id, event_id],
2702 |row| row.get(0),
2703 )
2704 .optional()
2705}
2706
2707pub fn create_filter(conn: &Connection, user_id: i64, definition: &str) -> rusqlite::Result<i64> {
2713 conn.execute("INSERT INTO filters (user_id, definition) VALUES (?1, ?2)", params![user_id, definition])?;
2714 Ok(conn.last_insert_rowid())
2715}
2716
2717pub fn get_filter(conn: &Connection, user_id: i64, filter_id: i64) -> rusqlite::Result<Option<String>> {
2720 conn.query_row(
2721 "SELECT definition FROM filters WHERE id = ?1 AND user_id = ?2",
2722 params![filter_id, user_id],
2723 |row| row.get(0),
2724 )
2725 .optional()
2726}
2727
2728pub fn insert_legacy_dm_message_map(conn: &Connection, legacy_message_id: i64, event_id: &str) -> rusqlite::Result<()> {
2733 conn.execute(
2734 "INSERT INTO legacy_dm_message_map (legacy_message_id, event_id) VALUES (?1, ?2)",
2735 params![legacy_message_id, event_id],
2736 )?;
2737 Ok(())
2738}
2739
2740pub fn legacy_dm_message_event_id(conn: &Connection, legacy_message_id: i64) -> rusqlite::Result<Option<String>> {
2741 conn.query_row(
2742 "SELECT event_id FROM legacy_dm_message_map WHERE legacy_message_id = ?1",
2743 params![legacy_message_id],
2744 |row| row.get(0),
2745 )
2746 .optional()
2747}
2748
2749pub fn highest_mapped_legacy_message_id(conn: &Connection, room_id: &str) -> rusqlite::Result<Option<i64>> {
2762 conn.query_row(
2763 "SELECT MAX(m.legacy_message_id) FROM legacy_dm_message_map m
2764 JOIN events e ON e.event_id = m.event_id
2765 WHERE e.room_id = ?1",
2766 params![room_id],
2767 |row| row.get(0),
2768 )
2769}
2770
2771#[derive(Debug, Clone, PartialEq)]
2807pub struct LegacyDmMessageImport {
2808 pub legacy_message_id: i64,
2809 pub event_id: String,
2810 pub sender_user_id: i64,
2811 pub content: String,
2812 pub origin_server_ts: i64,
2813}
2814
2815#[derive(Debug, Clone, PartialEq)]
2821pub struct LegacyDmReadReceipt {
2822 pub reader_user_id: i64,
2823 pub up_to_legacy_message_id: i64,
2824 pub ts_ms: i64,
2825}
2826
2827#[derive(Debug, Clone, PartialEq)]
2832pub struct LegacyDmDirectHint {
2833 pub user_id: i64,
2834 pub peer_mxid: String,
2835}
2836
2837#[derive(Debug, Clone, Copy)]
2842pub struct DmMigrationExtras<'a> {
2843 pub messages: &'a [LegacyDmMessageImport],
2844 pub receipts: &'a [LegacyDmReadReceipt],
2845 pub direct_hints: &'a [LegacyDmDirectHint],
2846}
2847
2848#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2850pub struct DmMigrationCounts {
2851 pub messages_imported: usize,
2852 pub receipts_set: usize,
2853}
2854
2855fn merge_m_direct_in_tx(tx: &Transaction, user_id: i64, peer_mxid: &str, room_id: &str) -> Result<(), MatrixStoreError> {
2860 let existing: Option<String> = tx
2861 .query_row(
2862 "SELECT content FROM account_data WHERE user_id = ?1 AND room_id = ?2 AND data_type = 'm.direct'",
2863 params![user_id, GLOBAL_ACCOUNT_DATA_ROOM],
2864 |row| row.get(0),
2865 )
2866 .optional()?;
2867
2868 let mut direct: serde_json::Map<String, serde_json::Value> = match existing {
2869 Some(content) => serde_json::from_str(&content)?,
2870 None => serde_json::Map::new(),
2871 };
2872 let rooms_entry = direct.entry(peer_mxid.to_string()).or_insert_with(|| serde_json::Value::Array(Vec::new()));
2873 if !rooms_entry.is_array() {
2874 *rooms_entry = serde_json::Value::Array(Vec::new());
2875 }
2876 if let serde_json::Value::Array(list) = rooms_entry {
2877 if !list.iter().any(|v| v.as_str() == Some(room_id)) {
2878 list.push(serde_json::Value::String(room_id.to_string()));
2879 }
2880 }
2881
2882 let stream_id = next_stream_id(tx)?;
2883 tx.execute(
2884 "INSERT INTO account_data (user_id, room_id, data_type, content, stream_id) VALUES (?1, ?2, 'm.direct', ?3, ?4)
2885 ON CONFLICT(user_id, room_id, data_type) DO UPDATE SET content = excluded.content, stream_id = excluded.stream_id",
2886 params![user_id, GLOBAL_ACCOUNT_DATA_ROOM, serde_json::Value::Object(direct).to_string(), stream_id],
2887 )?;
2888 Ok(())
2889}
2890
2891pub fn migrate_dm_conversation(
2906 conn: &mut Connection,
2907 bootstrap: RoomBootstrap<'_>,
2908 state_events: &[NewStateEvent],
2909 bootstrap_origin_server_ts: i64,
2910 extras: DmMigrationExtras<'_>,
2911) -> Result<(Room, DmMigrationCounts), MatrixStoreError> {
2912 let tx = conn.transaction()?;
2913
2914 insert_room_row(
2915 &tx,
2916 bootstrap.room_id,
2917 bootstrap.kind,
2918 bootstrap.creator_user_id,
2919 bootstrap.created_at,
2920 bootstrap.is_encrypted,
2921 bootstrap.join_rule,
2922 bootstrap.history_visibility,
2923 bootstrap.dm_pair_key,
2924 bootstrap.legacy_dm_id,
2925 )?;
2926 for event in state_events {
2927 apply_state_event_in_tx(
2928 &tx,
2929 &event.event_id,
2930 bootstrap.room_id,
2931 event.sender_user_id,
2932 &event.event_type,
2933 &event.state_key,
2934 &event.content,
2935 bootstrap_origin_server_ts,
2936 bootstrap.created_at,
2937 )?;
2938 }
2939
2940 let mut event_id_by_legacy_id: std::collections::HashMap<i64, String> = std::collections::HashMap::new();
2941 for message in extras.messages {
2942 insert_timeline_event_in_tx(
2943 &tx,
2944 &TimelineEventRow {
2945 event_id: &message.event_id,
2946 room_id: bootstrap.room_id,
2947 sender_user_id: message.sender_user_id,
2948 event_type: "org.example.legacy_dm",
2949 content: &message.content,
2950 origin_server_ts: message.origin_server_ts,
2951 txn_id: None,
2952 },
2953 )?;
2954 insert_legacy_dm_message_map(&tx, message.legacy_message_id, &message.event_id)?;
2955 event_id_by_legacy_id.insert(message.legacy_message_id, message.event_id.clone());
2956 }
2957
2958 let mut receipts_set = 0usize;
2959 for receipt in extras.receipts {
2960 let Some(target_event_id) = event_id_by_legacy_id.get(&receipt.up_to_legacy_message_id) else { continue };
2961 let target_stream_id: i64 =
2962 tx.query_row("SELECT stream_id FROM events WHERE event_id = ?1", params![target_event_id], |row| row.get(0))?;
2963 tx.execute(
2964 "INSERT INTO receipts (room_id, user_id, receipt_type, event_id, ts, stream_id) VALUES (?1, ?2, 'm.read', ?3, ?4, ?5)",
2965 params![bootstrap.room_id, receipt.reader_user_id, target_event_id, receipt.ts_ms, target_stream_id],
2966 )?;
2967 receipts_set += 1;
2968 }
2969
2970 for hint in extras.direct_hints {
2971 merge_m_direct_in_tx(&tx, hint.user_id, &hint.peer_mxid, bootstrap.room_id)?;
2972 }
2973
2974 tx.commit()?;
2975
2976 Ok((
2977 Room {
2978 id: bootstrap.room_id.to_string(),
2979 kind: bootstrap.kind,
2980 room_version: MATRIX_ROOM_VERSION.to_string(),
2981 creator_user_id: bootstrap.creator_user_id,
2982 created_at: bootstrap.created_at.to_string(),
2983 is_encrypted: bootstrap.is_encrypted,
2984 join_rule: bootstrap.join_rule,
2985 history_visibility: bootstrap.history_visibility,
2986 dm_pair_key: bootstrap.dm_pair_key.map(str::to_string),
2987 legacy_dm_id: bootstrap.legacy_dm_id,
2988 },
2989 DmMigrationCounts { messages_imported: extras.messages.len(), receipts_set },
2990 ))
2991}
2992
2993pub fn catch_up_dm_conversation(
3021 conn: &mut Connection,
3022 room_id: &str,
3023 messages: &[LegacyDmMessageImport],
3024 receipts: &[LegacyDmReadReceipt],
3025) -> Result<DmMigrationCounts, MatrixStoreError> {
3026 let tx = conn.transaction()?;
3027 let counts = catch_up_dm_in_tx(&tx, room_id, messages, receipts)?;
3028 tx.commit()?;
3029 Ok(counts)
3030}
3031
3032fn catch_up_dm_in_tx(
3037 tx: &Transaction,
3038 room_id: &str,
3039 messages: &[LegacyDmMessageImport],
3040 receipts: &[LegacyDmReadReceipt],
3041) -> Result<DmMigrationCounts, MatrixStoreError> {
3042 for message in messages {
3043 insert_timeline_event_in_tx(
3044 tx,
3045 &TimelineEventRow {
3046 event_id: &message.event_id,
3047 room_id,
3048 sender_user_id: message.sender_user_id,
3049 event_type: "org.example.legacy_dm",
3050 content: &message.content,
3051 origin_server_ts: message.origin_server_ts,
3052 txn_id: None,
3053 },
3054 )?;
3055 insert_legacy_dm_message_map(tx, message.legacy_message_id, &message.event_id)?;
3056 }
3057
3058 let mut receipts_set = 0usize;
3059 for receipt in receipts {
3060 let Some(target_event_id) = legacy_dm_message_event_id(tx, receipt.up_to_legacy_message_id)? else { continue };
3061 upsert_receipt_in_tx(tx, room_id, receipt.reader_user_id, ReceiptType::Read, &target_event_id, receipt.ts_ms)?;
3062 receipts_set += 1;
3063 }
3064
3065 Ok(DmMigrationCounts { messages_imported: messages.len(), receipts_set })
3066}
3067
3068#[derive(Debug, Clone, Copy)]
3072pub struct DmAdoption<'a> {
3073 pub room_id: &'a str,
3075 pub legacy_dm_id: i64,
3077 pub key_events: &'a [NewStateEvent],
3082 pub key_events_origin_server_ts: i64,
3083 pub now: &'a str,
3085}
3086
3087pub fn adopt_dm_room_for_legacy(
3101 conn: &mut Connection,
3102 adoption: DmAdoption<'_>,
3103 messages: &[LegacyDmMessageImport],
3104 receipts: &[LegacyDmReadReceipt],
3105) -> Result<Option<DmMigrationCounts>, MatrixStoreError> {
3106 let tx = conn.transaction()?;
3107
3108 let bound = tx.execute(
3109 "UPDATE rooms SET legacy_dm_id = ?1 WHERE id = ?2 AND kind = 'dm' AND legacy_dm_id IS NULL",
3110 params![adoption.legacy_dm_id, adoption.room_id],
3111 )?;
3112 if bound == 0 {
3113 return Ok(None);
3114 }
3115
3116 for event in adoption.key_events {
3117 if current_state_event(&tx, adoption.room_id, &event.event_type, &event.state_key)?.is_some() {
3118 continue;
3119 }
3120 apply_state_event_in_tx(
3121 &tx,
3122 &event.event_id,
3123 adoption.room_id,
3124 event.sender_user_id,
3125 &event.event_type,
3126 &event.state_key,
3127 &event.content,
3128 adoption.key_events_origin_server_ts,
3129 adoption.now,
3130 )?;
3131 }
3132
3133 let counts = catch_up_dm_in_tx(&tx, adoption.room_id, messages, receipts)?;
3134 tx.commit()?;
3135 Ok(Some(counts))
3136}
3137
3138#[derive(Debug, Clone, PartialEq)]
3150pub struct PublicRoomSummary {
3151 pub room_id: String,
3152 pub name: Option<String>,
3153 pub topic: Option<String>,
3154 pub num_joined_members: i64,
3155 pub world_readable: bool,
3156}
3157
3158pub fn public_rooms_page(
3179 conn: &Connection,
3180 after_room_id: Option<&str>,
3181 limit: usize,
3182 search_term: Option<&str>,
3183) -> Result<(Vec<PublicRoomSummary>, bool, i64), MatrixStoreError> {
3184 let mut stmt = conn.prepare(
3185 "SELECT r.id, r.history_visibility,
3186 (SELECT COUNT(*) FROM room_members WHERE room_id = r.id AND membership = 'join')
3187 FROM rooms r WHERE r.join_rule = 'public' ORDER BY r.id ASC",
3188 )?;
3189 let mut rows = stmt.query([])?;
3190 let mut all = Vec::new();
3191 while let Some(row) = rows.next()? {
3192 let room_id: String = row.get(0)?;
3193 let history_visibility_raw: String = row.get(1)?;
3194 let num_joined_members: i64 = row.get(2)?;
3195 let world_readable = history_visibility_raw == HistoryVisibility::WorldReadable.as_str();
3196
3197 let name = current_state_event(conn, &room_id, "m.room.name", "")?
3198 .and_then(|e| serde_json::from_str::<serde_json::Value>(&e.content).ok())
3199 .and_then(|v| v.get("name").and_then(|n| n.as_str()).map(str::to_string));
3200 let topic = current_state_event(conn, &room_id, "m.room.topic", "")?
3201 .and_then(|e| serde_json::from_str::<serde_json::Value>(&e.content).ok())
3202 .and_then(|v| v.get("topic").and_then(|t| t.as_str()).map(str::to_string));
3203
3204 all.push(PublicRoomSummary { room_id, name, topic, num_joined_members, world_readable });
3205 }
3206 drop(rows);
3207 drop(stmt);
3208
3209 let total_room_count_estimate = all.len() as i64;
3210
3211 let filtered: Vec<PublicRoomSummary> = match search_term {
3212 Some(term) if !term.is_empty() => {
3213 let term_lower = term.to_lowercase();
3214 all.into_iter().filter(|room| room.name.as_deref().is_some_and(|n| n.to_lowercase().contains(&term_lower))).collect()
3215 }
3216 _ => all,
3217 };
3218
3219 let start = match after_room_id {
3220 Some(cursor) => filtered.iter().position(|r| r.room_id == cursor).map_or(0, |idx| idx + 1),
3221 None => 0,
3222 };
3223 let has_more = filtered.len() > start + limit;
3224 let page: Vec<PublicRoomSummary> = filtered.into_iter().skip(start).take(limit).collect();
3225 Ok((page, has_more, total_room_count_estimate))
3226}
3227
3228#[cfg(test)]
3229mod tests {
3230 use super::*;
3231
3232 const T0: &str = "2026-09-24T00:00:00+00:00";
3233 const ROOM: &str = "!testroom:example.org";
3234
3235 fn test_conn() -> Connection {
3236 let conn = Connection::open_in_memory().expect("in-memory sqlite");
3237 create_matrix_schema(&conn).expect("schema");
3238 conn
3239 }
3240
3241 fn ensure_legacy_dm_map_table(conn: &Connection) {
3242 conn.execute_batch(
3243 "CREATE TABLE IF NOT EXISTS legacy_dm_message_map (
3244 legacy_message_id INTEGER PRIMARY KEY,
3245 event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id)
3246 );",
3247 )
3248 .expect("legacy map table for remaining unit tests");
3249 }
3250
3251 fn make_room(conn: &Connection) {
3252 create_room(conn, ROOM, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None).expect("create room");
3253 }
3254
3255 #[test]
3258 fn next_stream_id_is_strictly_monotonic_across_every_table() {
3259 let mut conn = test_conn();
3260 make_room(&conn);
3261
3262 let event = insert_timeline_event(&mut conn, "$event1", ROOM, 1, "m.room.message", "{}", 1000).expect("insert event");
3263 let account_data_stream = upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", "{}").expect("account data");
3264 let receipt_stream = upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, "$event1", 1500).expect("receipt");
3265
3266 assert!(event.stream_id < account_data_stream, "event {} should precede account data {}", event.stream_id, account_data_stream);
3267 assert!(
3268 account_data_stream < receipt_stream,
3269 "account data {account_data_stream} should precede receipt {receipt_stream}"
3270 );
3271 }
3272
3273 #[test]
3276 fn apply_state_event_replaces_current_state_but_keeps_history_in_events() {
3277 let mut conn = test_conn();
3278 make_room(&conn);
3279
3280 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");
3281 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");
3282
3283 let current = current_state_event(&conn, ROOM, "m.room.name", "").expect("query").expect("row exists");
3284 assert_eq!(current.event_id, "$e2");
3285 assert_eq!(current.content, r#"{"name":"second"}"#);
3286
3287 let e1 = get_event(&conn, "$e1").expect("get e1").expect("row exists");
3288 let e2 = get_event(&conn, "$e2").expect("get e2").expect("row exists");
3289 assert_eq!(e1.content, r#"{"name":"first"}"#);
3290 assert_eq!(e2.content, r#"{"name":"second"}"#);
3291 }
3292
3293 #[test]
3296 fn redact_event_strips_content_per_v11_allow_list_by_type() {
3297 let cases: Vec<(&str, &str, serde_json::Value)> = vec![
3298 (
3299 "m.room.member",
3300 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"}}}"#,
3301 serde_json::json!({
3302 "membership": "join",
3303 "join_authorised_via_users_server": "@x:example.org",
3304 "third_party_invite": {"signed": {"mxid": "@bob:example.org"}}
3305 }),
3306 ),
3307 (
3308 "m.room.power_levels",
3309 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"}"#,
3310 serde_json::json!({
3311 "ban": 50, "events": {}, "events_default": 0, "invite": 50, "kick": 50,
3312 "redact": 50, "state_default": 50, "users": {"@a:x": 100}, "users_default": 0
3313 }),
3314 ),
3315 (
3316 "m.room.history_visibility",
3317 r#"{"history_visibility":"shared","extra":"drop"}"#,
3318 serde_json::json!({"history_visibility": "shared"}),
3319 ),
3320 (
3325 "m.room.message",
3326 r#"{"body":"hi","msgtype":"m.text"}"#,
3327 serde_json::json!({}),
3328 ),
3329 ];
3330
3331 for (event_type, content, expected) in cases {
3332 let mut conn = test_conn();
3333 make_room(&conn);
3334 insert_timeline_event(&mut conn, "$target", ROOM, 1, event_type, content, 1000).expect("insert target");
3335 redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).expect("redact");
3336
3337 let target = get_event(&conn, "$target").expect("get target").expect("row exists");
3338 let got: serde_json::Value = serde_json::from_str(&target.content).expect("parse stripped content");
3339 assert_eq!(got, expected, "event_type={event_type}");
3340 assert_eq!(target.redacted_by.as_deref(), Some("$redaction"), "event_type={event_type}");
3341 }
3342 }
3343
3344 #[test]
3345 fn redaction_content_carries_the_target_as_redacts_for_every_write_path() {
3346 let mut conn = test_conn();
3347 make_room(&conn);
3348 insert_timeline_event(&mut conn, "$t1", ROOM, 1, "m.room.message", "{}", 1000).expect("insert t1");
3349 insert_timeline_event(&mut conn, "$t2", ROOM, 1, "m.room.message", "{}", 1100).expect("insert t2");
3350 insert_timeline_event(&mut conn, "$t3", ROOM, 1, "m.room.message", "{}", 1200).expect("insert t3");
3351
3352 let plain = redact_event(&mut conn, ROOM, "$t1", "$r1", 1, None, 2000).expect("plain redact");
3353 assert_eq!(serde_json::from_str::<serde_json::Value>(&plain.content).expect("json"), serde_json::json!({ "redacts": "$t1" }));
3354 assert_eq!(plain.redacts.as_deref(), Some("$t1"));
3355
3356 let marked = redact_event_marked(
3357 &mut conn,
3358 &Redaction { room_id: ROOM, target_event_id: "$t2", redaction_event_id: "$r2", sender_user_id: 1, reason: Some("spam"), origin_server_ts: 2100 },
3359 &serde_json::json!({ "org.example.site_moderation": true }),
3360 )
3361 .expect("marked redact");
3362 assert_eq!(
3363 serde_json::from_str::<serde_json::Value>(&marked.content).expect("json"),
3364 serde_json::json!({ "redacts": "$t2", "reason": "spam", "org.example.site_moderation": true })
3365 );
3366
3367 let deduped = redact_event_deduped(&mut conn, "DEV1", "txn-1", ROOM, "$t3", "$r3", 1, None, 2200, T0).expect("deduped redact");
3368 let DedupedWrite::New(event) = deduped else { panic!("first write is new") };
3369 let stored = get_event(&conn, &event.event_id).expect("get").expect("row exists");
3370 assert_eq!(serde_json::from_str::<serde_json::Value>(&stored.content).expect("json")["redacts"], "$t3");
3371 }
3372
3373 #[test]
3374 fn redact_content_per_v11_keeps_everything_for_m_room_create() {
3375 let content = r#"{"room_version":"11","creator":"@a:example.org"}"#;
3376 let stripped = redact_content_per_v11("m.room.create", content).expect("strip");
3377 let got: serde_json::Value = serde_json::from_str(&stripped).expect("parse");
3378 assert_eq!(got, serde_json::json!({"room_version": "11", "creator": "@a:example.org"}));
3379 }
3380
3381 #[test]
3384 fn txn_dedup_returns_the_same_event_id_on_a_repeated_txn_id() {
3385 let mut conn = test_conn();
3386 make_room(&conn);
3387
3388 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-1").expect("lookup 1"), TxnDedupEntry::NotSeen);
3389
3390 let event = {
3391 let tx = conn.transaction().expect("tx");
3392 let row = TimelineEventRow {
3393 event_id: "$e1",
3394 room_id: ROOM,
3395 sender_user_id: 1,
3396 event_type: "m.room.message",
3397 content: "{}",
3398 origin_server_ts: 1000,
3399 txn_id: Some("txn-1"),
3400 };
3401 let event = insert_timeline_event_in_tx(&tx, &row).expect("insert");
3402 tx.commit().expect("commit");
3403 event
3404 };
3405 txn_dedup_record(&conn, 1, "DEV1", "txn-1", Some(&event.event_id), T0).expect("record");
3406
3407 assert_eq!(
3408 txn_dedup_lookup(&conn, 1, "DEV1", "txn-1").expect("lookup 2"),
3409 TxnDedupEntry::Seen(Some(event.event_id.clone()))
3410 );
3411
3412 let count: i64 = conn.query_row("SELECT COUNT(*) FROM events", [], |row| row.get(0)).expect("count");
3413 assert_eq!(count, 1, "a repeated txn_id must never create a second events row");
3414 }
3415
3416 #[test]
3419 fn relations_index_populated_from_cleartext_relates_to_even_when_content_is_ciphertext() {
3420 let mut conn = test_conn();
3421 make_room(&conn);
3422 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3423
3424 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"}}"#;
3425 let reaction = insert_timeline_event(&mut conn, "$reaction", ROOM, 2, "m.room.encrypted", content, 2000).expect("insert reaction");
3426
3427 let (rel_type, target_id, agg_key): (String, String, Option<String>) = conn
3428 .query_row(
3429 "SELECT rel_type, target_id, agg_key FROM relations WHERE event_id = ?1",
3430 params![reaction.event_id],
3431 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
3432 )
3433 .expect("relation row exists");
3434 assert_eq!(rel_type, "m.annotation");
3435 assert_eq!(target_id, "$target");
3436 assert_eq!(agg_key.as_deref(), Some("a"));
3437 }
3438
3439 #[test]
3442 fn account_data_global_row_is_unique() {
3443 let mut conn = test_conn();
3444 upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", r#"{"v":1}"#).expect("first upsert");
3445 upsert_account_data(&mut conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct", r#"{"v":2}"#).expect("second upsert");
3446
3447 let count: i64 = conn
3448 .query_row(
3449 "SELECT COUNT(*) FROM account_data WHERE user_id = 1 AND room_id = ''",
3450 [],
3451 |row| row.get(0),
3452 )
3453 .expect("count");
3454 assert_eq!(count, 1, "two global upserts of the same type must leave one row");
3455
3456 let row = get_account_data(&conn, 1, GLOBAL_ACCOUNT_DATA_ROOM, "m.direct").expect("get").expect("row exists");
3457 assert_eq!(row.content, r#"{"v":2}"#);
3458 }
3459
3460 #[test]
3463 fn receipt_never_moves_backwards() {
3464 let mut conn = test_conn();
3465 make_room(&conn);
3466 let e1 = insert_timeline_event(&mut conn, "$e1", ROOM, 1, "m.room.message", "{}", 1000).expect("e1");
3467 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 1, "m.room.message", "{}", 2000).expect("e2");
3468
3469 upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &e2.event_id, 5000).expect("advance to e2");
3470 upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &e1.event_id, 6000).expect("attempted backward move is a no-op");
3471
3472 let receipt = get_receipt(&conn, ROOM, 9, ReceiptType::Read).expect("get").expect("row exists");
3473 assert_eq!(receipt.event_id, e2.event_id, "a receipt must never move back to an earlier event");
3474 }
3475
3476 #[test]
3479 fn receipt_further_ahead_of_the_two_types_wins_for_notification_count() {
3480 let mut conn = test_conn();
3481 make_room(&conn);
3482 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3483 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");
3484
3485 insert_timeline_event(&mut conn, "$e1", ROOM, 2, "m.room.message", "{}", 1000).expect("e1");
3486 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 2, "m.room.message", "{}", 2000).expect("e2");
3487 insert_timeline_event(&mut conn, "$e3", ROOM, 2, "m.room.message", "{}", 3000).expect("e3");
3488 let e4 = insert_timeline_event(&mut conn, "$e4", ROOM, 2, "m.room.message", "{}", 4000).expect("e4");
3489 let e5 = insert_timeline_event(&mut conn, "$e5", ROOM, 2, "m.room.message", "{}", 5000).expect("e5");
3490
3491 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, &e2.event_id, 2500).expect("read receipt");
3495 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::ReadPrivate, &e4.event_id, 4500).expect("private receipt further ahead");
3496
3497 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3498 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 1, "only $e5 is after the further-ahead receipt ($e4)");
3499
3500 upsert_receipt(&mut conn, ROOM, 1, ReceiptType::Read, &e5.event_id, 5500).expect("read receipt advances past e5");
3503 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 0, "m.read now covers every message");
3504 }
3505
3506 #[test]
3507 fn notification_count_ignores_own_state_and_reactions() {
3508 let mut conn = test_conn();
3509 make_room(&conn);
3510 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3511 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");
3512
3513 insert_timeline_event(&mut conn, "$bob-msg", ROOM, 2, "m.room.message", "{}", 1000).expect("bob message");
3515 insert_timeline_event(&mut conn, "$bob-enc", ROOM, 2, "m.room.encrypted", "{}", 1100).expect("bob encrypted");
3516 insert_timeline_event(&mut conn, "$bob-legacy", ROOM, 2, "org.example.legacy_dm", "{}", 1200).expect("bob legacy dm");
3517
3518 insert_timeline_event(&mut conn, "$alice-msg", ROOM, 1, "m.room.message", "{}", 1300).expect("alice's own message");
3520 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");
3521 insert_timeline_event(&mut conn, "$reaction", ROOM, 2, "m.reaction", "{}", 1500).expect("reaction");
3522
3523 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3524 assert_eq!(notification_count(&conn, &room, 1).expect("count"), 3, "only bob's 3 message-like sends count");
3525 }
3526
3527 #[test]
3528 fn notification_count_is_zero_for_a_non_member() {
3529 let mut conn = test_conn();
3530 make_room(&conn);
3531 insert_timeline_event(&mut conn, "$e1", ROOM, 2, "m.room.message", "{}", 1000).expect("e1");
3532
3533 let room = get_room(&conn, ROOM).expect("get room").expect("room exists");
3534 assert_eq!(notification_count(&conn, &room, 999).expect("count"), 0, "a caller with no membership row at all sees nothing");
3535 }
3536
3537 #[test]
3540 fn duplicate_annotation_is_refused() {
3541 let mut conn = test_conn();
3542 make_room(&conn);
3543 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3544
3545 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3546 insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).expect("first reaction");
3547
3548 let err = insert_timeline_event(&mut conn, "$react2", ROOM, 5, "m.reaction", content, 3000).unwrap_err();
3549 assert!(matches!(err, MatrixStoreError::DuplicateAnnotation));
3550
3551 assert_eq!(get_event(&conn, "$react2").expect("get"), None);
3553
3554 let other = insert_timeline_event(&mut conn, "$react3", ROOM, 6, "m.reaction", content, 4000).expect("different sender reacts");
3557 assert_eq!(other.event_id, "$react3");
3558 }
3559
3560 #[test]
3563 fn reannotation_after_redaction_is_allowed() {
3564 let mut conn = test_conn();
3565 make_room(&conn);
3566 insert_timeline_event(&mut conn, "$target", ROOM, 1, "m.room.message", "{}", 1000).expect("target");
3567
3568 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3569 let first = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).expect("first reaction");
3570
3571 redact_event(&mut conn, ROOM, &first.event_id, "$redaction", 1, None, 2500).expect("redact the reaction");
3572
3573 let second = insert_timeline_event(&mut conn, "$react2", ROOM, 5, "m.reaction", content, 3000)
3576 .expect("re-annotation after redaction must succeed");
3577 assert_eq!(second.event_id, "$react2");
3578 }
3579
3580 #[test]
3581 fn relation_target_must_exist() {
3582 let mut conn = test_conn();
3583 make_room(&conn);
3584
3585 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$missing","key":"a"}}"#;
3586 let err = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).unwrap_err();
3587 assert!(matches!(err, MatrixStoreError::InvalidRelationTarget(ref id) if id == "$missing"));
3588 }
3589
3590 #[test]
3591 fn relation_target_must_be_in_the_same_room() {
3592 let mut conn = test_conn();
3593 make_room(&conn);
3594 let other_room = format!("!other:{}", matrix_server_name());
3595 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3596 .expect("other room");
3597 insert_timeline_event(&mut conn, "$target", &other_room, 1, "m.room.message", "{}", 1000).expect("target in other room");
3598
3599 let content = r#"{"m.relates_to":{"rel_type":"m.annotation","event_id":"$target","key":"a"}}"#;
3600 let err = insert_timeline_event(&mut conn, "$react1", ROOM, 5, "m.reaction", content, 2000).unwrap_err();
3601 assert!(matches!(err, MatrixStoreError::InvalidRelationTarget(ref id) if id == "$target"));
3602 }
3603
3604 #[test]
3605 fn redact_event_refuses_a_target_in_a_different_room() {
3606 let mut conn = test_conn();
3607 make_room(&conn);
3608 let other_room = format!("!other:{}", matrix_server_name());
3609 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3610 .expect("other room");
3611 insert_timeline_event(&mut conn, "$target", &other_room, 1, "m.room.message", "{}", 1000).expect("target in other room");
3612
3613 let err = redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).unwrap_err();
3614 assert!(matches!(err, MatrixStoreError::WrongRoom(ref id) if id == "$target"));
3615 }
3616
3617 #[test]
3618 fn redact_event_refuses_create_and_encryption_events() {
3619 for event_type in ["m.room.create", "m.room.encryption"] {
3620 let mut conn = test_conn();
3621 make_room(&conn);
3622 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");
3623
3624 let err = redact_event(&mut conn, ROOM, "$target", "$redaction", 1, None, 2000).unwrap_err();
3625 assert!(
3626 matches!(err, MatrixStoreError::UnredactableEvent(ref t) if t == event_type),
3627 "event_type={event_type}"
3628 );
3629 }
3630 }
3631
3632 #[test]
3633 fn upsert_receipt_refuses_a_target_in_a_different_room() {
3634 let mut conn = test_conn();
3635 make_room(&conn);
3636 let other_room = format!("!other:{}", matrix_server_name());
3637 create_room(&conn, &other_room, RoomKind::Group, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None)
3638 .expect("other room");
3639 let event = insert_timeline_event(&mut conn, "$e1", &other_room, 1, "m.room.message", "{}", 1000).expect("event in other room");
3640
3641 let err = upsert_receipt(&mut conn, ROOM, 9, ReceiptType::Read, &event.event_id, 5000).unwrap_err();
3642 assert!(matches!(err, MatrixStoreError::WrongRoom(ref id) if id == &event.event_id));
3643 }
3644
3645 #[test]
3648 fn mxid_parse_refuses_foreign_server() {
3649 assert_eq!(public_id_from_mxid("@abc123:example.org"), Ok("abc123"));
3650 assert_eq!(public_id_from_mxid("@abc123:otherserver.example"), Err(MatrixIdError::ForeignServerName));
3651 let aliases = ["chat.example", "m4a.example.net", "m4a.example.org"];
3653 set_local_aliases(aliases.iter().map(|s| s.to_string()));
3654 for name in aliases {
3655 assert_eq!(public_id_from_mxid(&format!("@abc123:{name}")), Ok("abc123"));
3656 }
3657 assert_eq!(public_id_from_mxid("@abc123:evil.example"), Err(MatrixIdError::ForeignServerName));
3658 assert_eq!(mxid_for_public_id("abc123"), format!("@abc123:{}", matrix_server_name()), "minting never uses an alias");
3659 assert_eq!(public_id_from_mxid("abc123:example.org"), Err(MatrixIdError::MissingSigil));
3660 assert_eq!(public_id_from_mxid("@abc123"), Err(MatrixIdError::MissingServerName));
3661 }
3662
3663 #[test]
3666 fn ensure_matrix_user_is_idempotent_and_refuses_a_reserved_localpart() {
3667 let conn = test_conn();
3668 let mxid = ensure_matrix_user(&conn, 1, "abc123", T0).expect("first ensure");
3669 assert_eq!(mxid, "@abc123:example.org");
3670 let mxid_again = ensure_matrix_user(&conn, 1, "abc123", T0).expect("second ensure is a no-op");
3671 assert_eq!(mxid_again, mxid);
3672 assert_eq!(mxid_of(&conn, 1).expect("mxid_of"), Some(mxid.clone()));
3673 assert_eq!(user_id_of(&conn, &mxid).expect("user_id_of"), Some(1));
3674
3675 let err = ensure_matrix_user(&conn, 2, "_bridge_evil", T0).unwrap_err();
3676 assert!(matches!(err, MatrixStoreError::ReservedLocalpart));
3677 }
3678
3679 #[test]
3680 fn apply_state_event_member_refreshes_room_members_and_power_levels() {
3681 let mut conn = test_conn();
3682 make_room(&conn);
3683 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("alice");
3684 let bob = ensure_matrix_user(&conn, 2, "bob000000000000000000000000002", T0).expect("bob");
3685
3686 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");
3687 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");
3688
3689 let joined = room_members(&conn, ROOM, Some(Membership::Join)).expect("joined members");
3690 assert_eq!(joined.len(), 1);
3691 assert_eq!(joined[0].user_id, 1);
3692 assert_eq!(joined[0].power_level, None);
3693
3694 let power_levels_content = serde_json::json!({"users": {alice.clone(): 100}, "users_default": 0}).to_string();
3695 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");
3696
3697 let alice_row = room_members(&conn, ROOM, None)
3698 .expect("all members")
3699 .into_iter()
3700 .find(|m| m.user_id == 1)
3701 .expect("alice row");
3702 assert_eq!(alice_row.power_level, Some(100));
3703
3704 let bob_rooms = rooms_for_user(&conn, 2, Some(Membership::Invite)).expect("bob's invited rooms");
3705 assert_eq!(bob_rooms, vec![ROOM.to_string()]);
3706 }
3707
3708 #[test]
3709 fn events_in_room_after_and_before_page_in_the_documented_order() {
3710 let mut conn = test_conn();
3711 make_room(&conn);
3712 let e1 = insert_timeline_event(&mut conn, "$e1", ROOM, 1, "m.room.message", "{}", 1000).expect("e1");
3713 let e2 = insert_timeline_event(&mut conn, "$e2", ROOM, 1, "m.room.message", "{}", 2000).expect("e2");
3714 let e3 = insert_timeline_event(&mut conn, "$e3", ROOM, 1, "m.room.message", "{}", 3000).expect("e3");
3715
3716 let after = events_in_room_after(&conn, ROOM, e1.stream_id, 10).expect("after");
3717 assert_eq!(after.iter().map(|e| e.event_id.clone()).collect::<Vec<_>>(), vec![e2.event_id.clone(), e3.event_id.clone()]);
3718
3719 let before = events_in_room_before(&conn, ROOM, e3.stream_id, 10).expect("before");
3720 assert_eq!(before.iter().map(|e| e.event_id.clone()).collect::<Vec<_>>(), vec![e2.event_id.clone(), e1.event_id.clone()]);
3721
3722 assert_eq!(max_stream_id(&conn).expect("max"), e3.stream_id);
3723 }
3724
3725 #[test]
3726 fn txn_dedup_lookup_of_a_to_device_send_has_no_event_id() {
3727 let conn = test_conn();
3728 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-td").expect("lookup"), TxnDedupEntry::NotSeen);
3729 txn_dedup_record(&conn, 1, "DEV1", "txn-td", None, T0).expect("record to-device send");
3730 assert_eq!(txn_dedup_lookup(&conn, 1, "DEV1", "txn-td").expect("lookup again"), TxnDedupEntry::Seen(None));
3731 }
3732
3733 #[test]
3734 fn filters_create_and_get_are_scoped_to_their_owner() {
3735 let conn = test_conn();
3736 let filter_id = create_filter(&conn, 1, r#"{"room":{"timeline":{"limit":20}}}"#).expect("create");
3737 assert_eq!(get_filter(&conn, 1, filter_id).expect("owner reads it"), Some(r#"{"room":{"timeline":{"limit":20}}}"#.to_string()));
3738 assert_eq!(get_filter(&conn, 2, filter_id).expect("a different user cannot"), None);
3739 }
3740
3741 #[test]
3742 fn legacy_dm_message_map_insert_and_get_round_trip() {
3743 let mut conn = test_conn();
3744 make_room(&conn);
3745 ensure_legacy_dm_map_table(&conn);
3746 let event = insert_timeline_event(&mut conn, "$legacy1", ROOM, 1, "org.example.legacy_dm", "{}", 1000).expect("insert");
3747 insert_legacy_dm_message_map(&conn, 42, &event.event_id).expect("map insert");
3748 assert_eq!(legacy_dm_message_event_id(&conn, 42).expect("map get"), Some(event.event_id));
3749 assert_eq!(legacy_dm_message_event_id(&conn, 999).expect("missing"), None);
3750 }
3751
3752 #[test]
3755 fn power_level_defaults_apply_when_fields_missing() {
3756 let pl = serde_json::json!({});
3757 assert_eq!(user_level(&pl, "@nobody:example.org"), 0, "users_default defaults to 0");
3758 assert_eq!(event_level(&pl, "m.room.message", false), 0, "events_default defaults to 0");
3759 assert_eq!(event_level(&pl, "m.room.name", true), 50, "state_default defaults to 50");
3760 for action in [PowerAction::Invite, PowerAction::Kick, PowerAction::Ban, PowerAction::Redact, PowerAction::StateDefault] {
3761 assert!(!can(&pl, action, "@nobody:example.org"), "level 0 must not reach the default 50 threshold for {action:?}");
3762 }
3763
3764 let pl_with_creator = serde_json::json!({ "users": { "@creator:example.org": 100 } });
3765 assert!(can(&pl_with_creator, PowerAction::Ban, "@creator:example.org"));
3766 assert!(can(&pl_with_creator, PowerAction::StateDefault, "@creator:example.org"));
3767 }
3768
3769 #[test]
3770 fn event_level_uses_the_events_type_override_before_falling_back_to_a_default() {
3771 let pl = serde_json::json!({ "events": { "m.room.name": 60 }, "events_default": 0, "state_default": 50 });
3772 assert_eq!(event_level(&pl, "m.room.name", true), 60, "an explicit events[type] override wins");
3773 assert_eq!(event_level(&pl, "m.room.topic", true), 50, "an unlisted state type falls back to state_default");
3774 assert_eq!(event_level(&pl, "m.room.message", false), 0, "an unlisted timeline type falls back to events_default");
3775 }
3776
3777 #[test]
3780 fn can_act_on_requires_strictly_greater_level_except_self_leave() {
3781 let pl = serde_json::json!({
3782 "users": { "@owner:example.org": 100, "@admin:example.org": 50, "@peer:example.org": 50 },
3783 "kick": 50,
3784 "ban": 50,
3785 });
3786
3787 assert!(!can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@owner:example.org", false));
3790 assert!(!can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@peer:example.org", false));
3792 assert!(can_act_on(&pl, PowerAction::Kick, "@owner:example.org", "@admin:example.org", false));
3794 assert!(can_act_on(&pl, PowerAction::Kick, "@admin:example.org", "@admin:example.org", true));
3797 assert!(!can_act_on(&pl, PowerAction::Ban, "@admin:example.org", "@admin:example.org", false));
3799 }
3800
3801 #[test]
3802 fn validate_power_levels_change_refuses_raising_self_above_own_level() {
3803 let old = serde_json::json!({ "users": { "@admin:example.org": 50 } });
3804 let new = serde_json::json!({ "users": { "@admin:example.org": 100 } });
3805 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3806 }
3807
3808 #[test]
3809 fn validate_power_levels_change_refuses_demoting_a_peer_at_an_equal_level() {
3810 let old = serde_json::json!({ "users": { "@a:example.org": 50, "@b:example.org": 50 } });
3811 let new = serde_json::json!({ "users": { "@a:example.org": 50, "@b:example.org": 0 } });
3812 assert!(validate_power_levels_change(&old, &new, "@a:example.org").is_err());
3813 }
3814
3815 #[test]
3816 fn validate_power_levels_change_allows_demoting_self() {
3817 let old = serde_json::json!({ "users": { "@admin:example.org": 50 } });
3818 let new = serde_json::json!({ "users": { "@admin:example.org": 10 } });
3819 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_ok());
3820 }
3821
3822 #[test]
3823 fn validate_power_levels_change_refuses_raising_events_default_above_own_level() {
3824 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "events_default": 0 });
3825 let new = serde_json::json!({ "users": { "@admin:example.org": 50 }, "events_default": 60 });
3826 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3827 }
3828
3829 #[test]
3830 fn validate_power_levels_change_allows_the_owner_changing_anything_up_to_their_own_level() {
3831 let old = serde_json::json!({ "users": { "@owner:example.org": 100, "@a:example.org": 50 } });
3832 let new = serde_json::json!({
3833 "users": { "@owner:example.org": 100, "@a:example.org": 90 },
3834 "ban": 100,
3835 "kick": 100,
3836 "events_default": 100,
3837 });
3838 assert!(validate_power_levels_change(&old, &new, "@owner:example.org").is_ok());
3839 }
3840
3841 #[test]
3842 fn validate_power_levels_change_refuses_a_scalar_field_change_above_own_level() {
3843 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 50 });
3844 let new = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 75 });
3845 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_err());
3846 }
3847
3848 #[test]
3849 fn validate_power_levels_change_ignores_unchanged_fields() {
3850 let old = serde_json::json!({ "users": { "@admin:example.org": 50 }, "ban": 50, "events": { "m.room.name": 40 } });
3851 let new = old.clone();
3852 assert!(validate_power_levels_change(&old, &new, "@admin:example.org").is_ok());
3853 }
3854
3855 fn bootstrap<'a>(room_id: &'a str, kind: RoomKind, creator: i64) -> RoomBootstrap<'a> {
3858 RoomBootstrap {
3859 room_id,
3860 kind,
3861 creator_user_id: creator,
3862 created_at: T0,
3863 is_encrypted: true,
3864 join_rule: if kind == RoomKind::Channel { JoinRule::Public } else { JoinRule::Invite },
3865 history_visibility: HistoryVisibility::Shared,
3866 dm_pair_key: None,
3867 legacy_dm_id: None,
3868 }
3869 }
3870
3871 fn state_event(event_id: &str, sender: i64, event_type: &str, state_key: &str, content: &str) -> NewStateEvent {
3872 NewStateEvent {
3873 event_id: event_id.to_string(),
3874 sender_user_id: sender,
3875 event_type: event_type.to_string(),
3876 state_key: state_key.to_string(),
3877 content: content.to_string(),
3878 }
3879 }
3880
3881 #[test]
3882 fn create_room_with_state_inserts_the_room_and_every_bootstrap_event_atomically() {
3883 let mut conn = test_conn();
3884 let creator_mxid = ensure_matrix_user(&conn, 1, "creator0000000000000000000001", T0).expect("creator");
3885 let room_id = "!batch:example.org";
3886
3887 let events = vec![
3888 state_event("$create", 1, "m.room.create", "", r#"{"room_version":"11"}"#),
3889 state_event("$m1", 1, "m.room.member", &creator_mxid, r#"{"membership":"join"}"#),
3890 state_event(
3891 "$pl",
3892 1,
3893 "m.room.power_levels",
3894 "",
3895 &serde_json::json!({"users": {creator_mxid.clone(): 100}, "users_default": 0}).to_string(),
3896 ),
3897 ];
3898
3899 let (room, applied) = create_room_with_state(&mut conn, bootstrap(room_id, RoomKind::Group, 1), &events, 1000).expect("create batch");
3900 assert_eq!(room.id, room_id);
3901 assert_eq!(applied.len(), 3);
3902 assert!(get_room(&conn, room_id).expect("get room").is_some());
3903 for event in &applied {
3904 assert!(get_event(&conn, &event.event_id).expect("get event").is_some());
3905 }
3906 let creator_row = room_member(&conn, room_id, 1).expect("member row").expect("row exists");
3907 assert_eq!(creator_row.power_level, Some(100), "power_levels applied after the member row existed");
3908 }
3909
3910 #[test]
3911 fn create_room_is_atomic_on_failure() {
3912 let mut conn = test_conn();
3913 let creator_mxid = ensure_matrix_user(&conn, 1, "creator0000000000000000000002", T0).expect("creator");
3914 let room_id = "!atomic:example.org";
3915
3916 let events = vec![
3917 state_event("$create", 1, "m.room.create", "", r#"{"room_version":"11"}"#),
3918 state_event("$m1", 1, "m.room.member", &creator_mxid, r#"{"membership":"join"}"#),
3919 state_event("$bad", 1, "m.room.member", "@ghost:example.org", r#"{"membership":"invite"}"#),
3922 ];
3923
3924 let err = create_room_with_state(&mut conn, bootstrap(room_id, RoomKind::Group, 1), &events, 1000).unwrap_err();
3925 assert!(matches!(err, MatrixStoreError::UnknownMxid(ref m) if m == "@ghost:example.org"));
3926
3927 assert_eq!(get_room(&conn, room_id).expect("get room"), None, "a failed batch must leave no room row");
3928 assert_eq!(get_event(&conn, "$create").expect("get"), None, "a failed batch must leave no event rows at all");
3929 assert_eq!(get_event(&conn, "$m1").expect("get"), None);
3930 }
3931
3932 #[test]
3935 fn dm_pair_key_reuse_is_freed_once_the_room_is_not_reused() {
3936 let conn = test_conn();
3937 create_room(&conn, "!dm1:example.org", RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None)
3938 .expect("first dm room");
3939 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup").map(|r| r.id), Some("!dm1:example.org".to_string()));
3940
3941 clear_dm_pair_key(&conn, "!dm1:example.org").expect("clear");
3942 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup after clear"), None);
3943
3944 create_room(&conn, "!dm2:example.org", RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None)
3946 .expect("second dm room reuses the freed pair key");
3947 assert_eq!(room_by_dm_pair_key(&conn, "1:2").expect("lookup").map(|r| r.id), Some("!dm2:example.org".to_string()));
3948 }
3949
3950 #[test]
3953 fn room_member_finds_the_one_row_forget_membership_deletes_only_when_left() {
3954 let mut conn = test_conn();
3955 make_room(&conn);
3956 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000099", T0).expect("alice");
3957 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");
3958
3959 assert_eq!(room_member(&conn, ROOM, 1).expect("member").map(|m| m.membership), Some(Membership::Join));
3960 assert_eq!(room_member(&conn, ROOM, 999).expect("no such member"), None);
3961
3962 assert_eq!(forget_membership(&conn, ROOM, 1).expect("forget while joined"), 0);
3965 assert!(room_member(&conn, ROOM, 1).expect("still a member").is_some());
3966
3967 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");
3968 assert_eq!(forget_membership(&conn, ROOM, 1).expect("forget after leaving"), 1);
3969 assert_eq!(room_member(&conn, ROOM, 1).expect("gone"), None);
3970 }
3971
3972 #[test]
3975 fn state_events_of_type_at_excludes_state_keys_created_after_the_cutoff() {
3976 let mut conn = test_conn();
3977 make_room(&conn);
3978 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000098", T0).expect("alice");
3979 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000098", T0).expect("bob");
3980
3981 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");
3982 let cutoff = e1.stream_id;
3983 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");
3984
3985 let at_cutoff = state_events_of_type_at(&conn, ROOM, "m.room.member", cutoff).expect("at cutoff");
3986 assert_eq!(at_cutoff.len(), 1, "bob's invite lands strictly after the cutoff and must be excluded");
3987 assert_eq!(at_cutoff[0].event_id, "$m1");
3988
3989 let after_both = state_events_of_type_at(&conn, ROOM, "m.room.member", cutoff + 1).expect("after both");
3990 assert_eq!(after_both.len(), 2);
3991 }
3992
3993 #[test]
3996 fn stripped_invite_state_includes_room_basics_and_the_inviters_own_member_event() {
3997 let mut conn = test_conn();
3998 make_room(&conn);
3999 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000097", T0).expect("alice");
4000 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000097", T0).expect("bob");
4001
4002 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");
4003 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");
4004 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");
4005 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");
4006
4007 let stripped = stripped_invite_state(&conn, ROOM, 1).expect("stripped state");
4008 let types: Vec<&str> = stripped.iter().map(|v| v["type"].as_str().expect("type")).collect();
4009 assert!(types.contains(&"m.room.create"));
4010 assert!(types.contains(&"m.room.join_rules"));
4011 assert!(!types.contains(&"m.room.encryption"), "no encryption event exists in this room");
4012
4013 let inviter_member = stripped
4014 .iter()
4015 .find(|v| v["type"] == "m.room.member" && v["state_key"] == alice)
4016 .expect("the inviter's own member event is included");
4017 assert_eq!(inviter_member["sender"], alice);
4018 assert_eq!(inviter_member["content"]["membership"], "join");
4019 }
4020
4021 #[test]
4024 fn user_ids_with_leave_transition_in_rooms_finds_only_leave_and_ban_inside_the_window() {
4025 let mut conn = test_conn();
4026 make_room(&conn);
4027 let alice = ensure_matrix_user(&conn, 2, "alice00000000000000000000000097", T0).expect("alice");
4028 let bob = ensure_matrix_user(&conn, 3, "bob0000000000000000000000000097", T0).expect("bob");
4029 let carol = ensure_matrix_user(&conn, 4, "carol0000000000000000000000097a", T0).expect("carol");
4030
4031 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");
4032 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 })
4033 .expect("bob joins")
4034 .stream_id;
4035 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");
4036 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");
4037 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)");
4038
4039 let mut left = user_ids_with_leave_transition_in_rooms(&conn, &[ROOM.to_string()], boundary, i64::MAX).expect("query");
4040 left.sort_unstable();
4041 assert_eq!(left, vec![2, 3], "alice (leave) and bob (ban) both count; carol's join does not");
4042
4043 let empty = user_ids_with_leave_transition_in_rooms(&conn, &[], 0, i64::MAX).expect("empty room set");
4044 assert!(empty.is_empty());
4045 }
4046
4047 #[test]
4050 fn membership_at_reads_the_state_as_of_a_stream_position() {
4051 let mut conn = test_conn();
4052 make_room(&conn);
4053 let alice = ensure_matrix_user(&conn, 2, "alice00000000000000000000000081", T0).expect("alice");
4054 let bob = ensure_matrix_user(&conn, 3, "bob0000000000000000000000000081", T0).expect("bob");
4055
4056 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;
4057 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;
4058 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;
4059
4060 assert_eq!(membership_at(&conn, ROOM, &bob, alice_joined).expect("query"), None, "bob had no member event yet");
4061 assert_eq!(membership_at(&conn, ROOM, &bob, bob_invited).expect("query"), Some(Membership::Invite));
4062 assert_eq!(membership_at(&conn, ROOM, &bob, bob_joined).expect("query"), Some(Membership::Join));
4063 assert_eq!(membership_at(&conn, ROOM, &alice, bob_invited).expect("query"), Some(Membership::Join));
4064
4065 let mut window = member_state_keys_in_window(&conn, ROOM, bob_invited, bob_joined).expect("window");
4066 window.sort();
4067 assert_eq!(window, vec![bob.clone()], "only bob has a member event after `bob_invited`");
4068 let mut all = member_state_keys_in_window(&conn, ROOM, 0, bob_joined).expect("whole history");
4069 all.sort();
4070 let mut expected = vec![alice, bob];
4071 expected.sort();
4072 assert_eq!(all, expected);
4073
4074 let rooms = [ROOM.to_string(), "!other:example.org".to_string()];
4075 assert_eq!(rooms_with_member_events_in_window(&conn, &rooms, bob_invited, bob_joined).expect("rooms"), vec![ROOM.to_string()]);
4076 assert!(rooms_with_member_events_in_window(&conn, &rooms, bob_joined, i64::MAX).expect("rooms after the last member event").is_empty());
4077 assert!(rooms_with_member_events_in_window(&conn, &[], 0, i64::MAX).expect("empty room set").is_empty());
4078 }
4079
4080 const ROOM_A: &str = "!roomA:example.org";
4083 const ROOM_B: &str = "!roomB:example.org";
4084 const ROOM_C: &str = "!roomC:example.org";
4085
4086 fn make_room_with_kind(conn: &Connection, room_id: &str, kind: RoomKind) {
4087 create_room(conn, room_id, kind, 1, T0, false, JoinRule::Invite, HistoryVisibility::Shared, None, None).expect("create room");
4088 }
4089
4090 fn member_content_in(conn: &Connection, room_id: &str, mxid: &str) -> serde_json::Value {
4091 let event = current_state_event(conn, room_id, "m.room.member", mxid).expect("query").expect("member event exists");
4092 serde_json::from_str(&event.content).expect("member content is json")
4093 }
4094
4095 fn member_event_count(conn: &Connection, room_id: &str, mxid: &str) -> i64 {
4096 conn.query_row(
4097 "SELECT COUNT(*) FROM events WHERE room_id = ?1 AND event_type = 'm.room.member' AND state_key = ?2",
4098 params![room_id, mxid],
4099 |row| row.get(0),
4100 )
4101 .expect("count member events")
4102 }
4103
4104 #[test]
4105 fn refresh_member_displayname_restamps_every_joined_room_and_skips_a_room_the_user_left() {
4106 let mut conn = test_conn();
4107 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000091", T0).expect("alice");
4108 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000091", T0).expect("bob");
4109 for room in [ROOM_A, ROOM_B, ROOM_C] {
4110 make_room_with_kind(&conn, room, RoomKind::Group);
4111 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 })
4112 .expect("alice joins");
4113 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 })
4114 .expect("bob joins");
4115 }
4116 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");
4117
4118 let refresh = refresh_member_displayname(&mut conn, 1, "new_alice", T0, 5000).expect("refresh");
4119
4120 assert_eq!(refresh.rooms_updated, 2, "the two rooms alice is still joined in");
4121 for room in [ROOM_A, ROOM_B] {
4122 let content = member_content_in(&conn, room, &alice);
4123 assert_eq!(content["membership"], "join");
4124 assert_eq!(content["displayname"], "new_alice");
4125 assert_eq!(member_event_count(&conn, room, &alice), 2, "a NEW member event lands; the old one stays in history");
4126 let event = current_state_event(&conn, room, "m.room.member", &alice).expect("query").expect("exists");
4127 assert_eq!(event.sender_user_id, 1, "a join refresh is sent by the user themself");
4128 assert_eq!(event.origin_server_ts, 5000);
4129 assert_eq!(member_content_in(&conn, room, &bob)["displayname"], "bob_nick", "another member's event is never touched");
4130 }
4131 assert_eq!(member_content_in(&conn, ROOM_C, &alice), serde_json::json!({ "membership": "leave" }), "the left room gets no new event");
4132 assert_eq!(member_event_count(&conn, ROOM_C, &alice), 2, "join + leave, nothing more");
4133 assert_eq!(refresh.affected_user_ids, HashSet::from([1, 2]), "alice and bob are woken; room C's members are not part of it");
4134
4135 let repeat = refresh_member_displayname(&mut conn, 1, "new_alice", T0, 6000).expect("second pass");
4136 assert_eq!(repeat, DisplaynameRefresh::default(), "an up-to-date event is skipped, so a repeat pass writes nothing");
4137 assert_eq!(member_event_count(&conn, ROOM_A, &alice), 2);
4138 }
4139
4140 #[test]
4141 fn refresh_member_displayname_keeps_an_invite_events_sender_and_is_direct() {
4142 let mut conn = test_conn();
4143 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000092", T0).expect("alice");
4144 let bob = ensure_matrix_user(&conn, 2, "bob0000000000000000000000000092", T0).expect("bob");
4145 make_room_with_kind(&conn, ROOM_A, RoomKind::Dm);
4146 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 })
4147 .expect("alice joins");
4148 apply_state_event(
4149 &mut conn,
4150 &StateEventWrite {
4151 event_id: &new_event_id(),
4152 room_id: ROOM_A,
4153 sender_user_id: 1,
4154 event_type: "m.room.member",
4155 state_key: &bob,
4156 content: r#"{"membership":"invite","is_direct":true}"#,
4157 origin_server_ts: 1100,
4158 now: T0,
4159 },
4160 )
4161 .expect("alice invites bob");
4162
4163 let refresh = refresh_member_displayname(&mut conn, 2, "bob_nick", T0, 5000).expect("refresh");
4164
4165 assert_eq!(refresh.rooms_updated, 1);
4166 let content = member_content_in(&conn, ROOM_A, &bob);
4167 assert_eq!(content, serde_json::json!({ "membership": "invite", "is_direct": true, "displayname": "bob_nick" }));
4168 let event = current_state_event(&conn, ROOM_A, "m.room.member", &bob).expect("query").expect("exists");
4169 assert_eq!(event.sender_user_id, 1, "the inviter stays the sender: stripped invite state reads the inviter off it");
4170 assert_eq!(room_member(&conn, ROOM_A, 2).expect("query").expect("row").membership, Membership::Invite, "still an invitation");
4171 assert_eq!(refresh.affected_user_ids, HashSet::from([1, 2]));
4172 }
4173
4174 #[test]
4175 fn refresh_member_displayname_is_a_noop_without_a_matrix_user_or_a_label() {
4176 let mut conn = test_conn();
4177 let alice = ensure_matrix_user(&conn, 1, "alice00000000000000000000000093", T0).expect("alice");
4178 make_room_with_kind(&conn, ROOM_A, RoomKind::Group);
4179 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");
4180
4181 assert_eq!(refresh_member_displayname(&mut conn, 99, "ghost", T0, 5000).expect("unknown user"), DisplaynameRefresh::default());
4182 assert_eq!(refresh_member_displayname(&mut conn, 1, "", T0, 5000).expect("empty label"), DisplaynameRefresh::default());
4183 assert_eq!(member_event_count(&conn, ROOM_A, &alice), 1);
4184 assert_eq!(matrix_user_ids(&conn).expect("ids"), vec![1]);
4185 }
4186
4187 fn native_dm_room(conn: &Connection) {
4190 create_room(conn, ROOM, RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), None).expect("create dm room");
4191 }
4192
4193 fn legacy_import(legacy_message_id: i64, event_id: &str) -> LegacyDmMessageImport {
4194 LegacyDmMessageImport {
4195 legacy_message_id,
4196 event_id: event_id.to_string(),
4197 sender_user_id: 1,
4198 content: "{}".to_string(),
4199 origin_server_ts: 1000 + legacy_message_id,
4200 }
4201 }
4202
4203 fn adoption_of(legacy_dm_id: i64) -> DmAdoption<'static> {
4204 DmAdoption { room_id: ROOM, legacy_dm_id, key_events: &[], key_events_origin_server_ts: 500, now: T0 }
4205 }
4206
4207 #[test]
4208 fn adopt_dm_room_for_legacy_binds_the_room_and_imports_the_messages() {
4209 let mut conn = test_conn();
4210 ensure_legacy_dm_map_table(&conn);
4211 native_dm_room(&conn);
4212
4213 let counts = adopt_dm_room_for_legacy(&mut conn, adoption_of(7), &[legacy_import(1, "$l1"), legacy_import(2, "$l2")], &[])
4214 .expect("adopt")
4215 .expect("the room is adoptable");
4216 assert_eq!(counts.messages_imported, 2);
4217 assert_eq!(room_by_legacy_dm_id(&conn, 7).expect("query").expect("bound").id, ROOM);
4218 assert_eq!(highest_mapped_legacy_message_id(&conn, ROOM).expect("query"), Some(2));
4219 assert_eq!(get_event(&conn, "$l1").expect("query").expect("imported").room_id, ROOM);
4220 }
4221
4222 #[test]
4223 fn adopt_dm_room_for_legacy_writes_a_key_event_only_where_the_room_has_none() {
4224 let mut conn = test_conn();
4225 ensure_legacy_dm_map_table(&conn);
4226 native_dm_room(&conn);
4227 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 })
4228 .expect("existing key event");
4229 let key_events = [
4230 NewStateEvent {
4231 event_id: "$k-a".to_string(),
4232 sender_user_id: 1,
4233 event_type: "org.example.legacy_dm_key".to_string(),
4234 state_key: "@a:example.org".to_string(),
4235 content: r#"{"public_key_b64":"BBBB"}"#.to_string(),
4236 },
4237 NewStateEvent {
4238 event_id: "$k-b".to_string(),
4239 sender_user_id: 2,
4240 event_type: "org.example.legacy_dm_key".to_string(),
4241 state_key: "@b:example.org".to_string(),
4242 content: r#"{"public_key_b64":"CCCC"}"#.to_string(),
4243 },
4244 ];
4245 let adoption = DmAdoption { key_events: &key_events, ..adoption_of(7) };
4246 adopt_dm_room_for_legacy(&mut conn, adoption, &[], &[]).expect("adopt").expect("adoptable");
4247
4248 let a = current_state_event(&conn, ROOM, "org.example.legacy_dm_key", "@a:example.org").expect("query").expect("still present");
4249 assert_eq!(a.event_id, existing.event_id, "an existing key event is never overwritten");
4250 let b = current_state_event(&conn, ROOM, "org.example.legacy_dm_key", "@b:example.org").expect("query").expect("added");
4251 assert_eq!(b.event_id, "$k-b");
4252 }
4253
4254 #[test]
4255 fn adopt_dm_room_for_legacy_refuses_a_room_that_is_bound_or_not_a_dm() {
4256 let mut conn = test_conn();
4257 ensure_legacy_dm_map_table(&conn);
4258 make_room(&conn);
4259 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");
4260 assert!(room_by_legacy_dm_id(&conn, 7).expect("query").is_none());
4261
4262 let mut bound = test_conn();
4263 create_room(&bound, ROOM, RoomKind::Dm, 1, T0, true, JoinRule::Invite, HistoryVisibility::Shared, Some("1:2"), Some(9)).expect("create bound room");
4264 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");
4265 assert_eq!(room_by_legacy_dm_id(&bound, 9).expect("query").expect("still bound to 9").id, ROOM);
4266 assert!(get_event(&bound, "$l1").expect("query").is_none(), "a refused adoption writes nothing");
4267 }
4268
4269 #[test]
4270 fn adopt_dm_room_for_legacy_rolls_back_the_binding_when_an_import_fails() {
4271 let mut conn = test_conn();
4272 ensure_legacy_dm_map_table(&conn);
4273 native_dm_room(&conn);
4274
4275 let result = adopt_dm_room_for_legacy(&mut conn, adoption_of(7), &[legacy_import(1, "$dup"), legacy_import(2, "$dup")], &[]);
4278 assert!(result.is_err());
4279
4280 assert!(room_by_legacy_dm_id(&conn, 7).expect("query").is_none(), "the binding must roll back with the failed import");
4281 assert!(get_event(&conn, "$dup").expect("query").is_none(), "no imported event survives");
4282 assert_eq!(highest_mapped_legacy_message_id(&conn, ROOM).expect("query"), None);
4283 }
4284}