[Add] Better key rotation, more relays (Sealed Sender, not yet fully)
This commit is contained in:
parent
b38b68ad96
commit
d8ef068a48
9 changed files with 924 additions and 125 deletions
|
|
@ -68,6 +68,7 @@ pub struct StoredMessage {
|
|||
pub sent_by_self: bool,
|
||||
pub message_state: String,
|
||||
pub height: i64,
|
||||
pub key_version: i64,
|
||||
pub reply_to: Option<i64>,
|
||||
pub reactions: Vec<StoredReaction>,
|
||||
}
|
||||
|
|
@ -82,6 +83,7 @@ pub struct NewMessage<'a> {
|
|||
pub sent_by_self: bool,
|
||||
pub content: &'a str,
|
||||
pub height: i64,
|
||||
pub key_version: i64,
|
||||
pub reply_to: Option<i64>,
|
||||
pub origin_iota_received_at: Option<i64>,
|
||||
pub destination_iota_received_at: Option<i64>,
|
||||
|
|
@ -596,6 +598,7 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
|
|||
sent_by_self,
|
||||
content,
|
||||
height,
|
||||
key_version,
|
||||
reply_to,
|
||||
origin_iota_received_at,
|
||||
destination_iota_received_at,
|
||||
|
|
@ -617,9 +620,9 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
|
|||
r#"
|
||||
INSERT INTO messages (
|
||||
storage_owner, external_user, message_time, content, sent_by_self,
|
||||
message_state, height, reply_to, relay_signer_id, relay_message_id,
|
||||
message_state, height, key_version, reply_to, relay_signer_id, relay_message_id,
|
||||
authored_at, origin_iota_received_at, destination_iota_received_at, stored_at, expires_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
|
||||
"#,
|
||||
params![
|
||||
storage_owner,
|
||||
|
|
@ -629,6 +632,7 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
|
|||
i64::from(sent_by_self),
|
||||
initial_state.as_str(),
|
||||
height,
|
||||
key_version,
|
||||
reply_to,
|
||||
relay_signer_id,
|
||||
relay_message_id,
|
||||
|
|
@ -960,7 +964,7 @@ pub fn get_messages(
|
|||
SELECT id, relay_signer_id, relay_message_id, message_time, authored_at,
|
||||
origin_iota_received_at, destination_iota_received_at,
|
||||
client_received_at, client_received_recorded_at, read_at,
|
||||
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height,
|
||||
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version,
|
||||
reply_to, edited_count
|
||||
FROM messages
|
||||
WHERE storage_owner = ?1 AND external_user = ?2 AND deleted_by_external = 0 AND history_deleted = 0
|
||||
|
|
@ -991,8 +995,9 @@ pub fn get_messages(
|
|||
sent_by_self: row.get::<_, i64>(14)? != 0,
|
||||
message_state: row.get(15)?,
|
||||
height: row.get(16).unwrap_or(0),
|
||||
reply_to: row.get(17).ok().flatten(),
|
||||
edited: row.get::<_, i64>(18).unwrap_or(0) > 0,
|
||||
key_version: row.get(17).unwrap_or(1),
|
||||
reply_to: row.get(18).ok().flatten(),
|
||||
edited: row.get::<_, i64>(19).unwrap_or(0) > 0,
|
||||
reactions: Vec::new(),
|
||||
})
|
||||
},
|
||||
|
|
@ -1033,7 +1038,7 @@ pub fn get_message(
|
|||
SELECT id, relay_signer_id, relay_message_id, message_time, authored_at,
|
||||
origin_iota_received_at, destination_iota_received_at,
|
||||
client_received_at, client_received_recorded_at, read_at,
|
||||
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height,
|
||||
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version,
|
||||
reply_to, edited_count, external_user
|
||||
FROM messages
|
||||
WHERE storage_owner = ?1
|
||||
|
|
@ -1064,9 +1069,10 @@ pub fn get_message(
|
|||
sent_by_self: row.get::<_, i64>(14)? != 0,
|
||||
message_state: row.get(15)?,
|
||||
height: row.get(16).unwrap_or(0),
|
||||
reply_to: row.get(17).ok().flatten(),
|
||||
edited: row.get::<_, i64>(18).unwrap_or(0) > 0,
|
||||
external_user: row.get(19)?,
|
||||
key_version: row.get(17).unwrap_or(1),
|
||||
reply_to: row.get(18).ok().flatten(),
|
||||
edited: row.get::<_, i64>(19).unwrap_or(0) > 0,
|
||||
external_user: row.get(20)?,
|
||||
reactions: Vec::new(),
|
||||
})
|
||||
})?;
|
||||
|
|
@ -1138,9 +1144,9 @@ pub fn get_messages_by_ids(storage_owner: i64, ids: &[i64]) -> Vec<StoredMessage
|
|||
// A journal id uniquely identifies a row. Load all messages for this owner and retain only
|
||||
// those ids; this keeps reaction hydration identical to normal message loading.
|
||||
match db::with_db(|conn| {
|
||||
let mut stmt = conn.prepare("SELECT id, relay_signer_id, relay_message_id, message_time, authored_at, origin_iota_received_at, destination_iota_received_at, client_received_at, client_received_recorded_at, read_at, read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, reply_to, edited_count, external_user FROM messages WHERE storage_owner = ?1 AND deleted_by_external = 0 AND history_deleted = 0")?;
|
||||
let mut stmt = conn.prepare("SELECT id, relay_signer_id, relay_message_id, message_time, authored_at, origin_iota_received_at, destination_iota_received_at, client_received_at, client_received_recorded_at, read_at, read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version, reply_to, edited_count, external_user FROM messages WHERE storage_owner = ?1 AND deleted_by_external = 0 AND history_deleted = 0")?;
|
||||
let rows = stmt.query_map([storage_owner], |row| {
|
||||
let external_user: i64 = row.get(19)?;
|
||||
let external_user: i64 = row.get(20)?;
|
||||
Ok(StoredMessage {
|
||||
id: row.get(0)?,
|
||||
external_user,
|
||||
|
|
@ -1160,8 +1166,9 @@ pub fn get_messages_by_ids(storage_owner: i64, ids: &[i64]) -> Vec<StoredMessage
|
|||
sent_by_self: row.get::<_, i64>(14)? != 0,
|
||||
message_state: row.get(15)?,
|
||||
height: row.get(16).unwrap_or(0),
|
||||
reply_to: row.get(17).ok().flatten(),
|
||||
edited: row.get::<_, i64>(18).unwrap_or(0) > 0,
|
||||
key_version: row.get(17).unwrap_or(1),
|
||||
reply_to: row.get(18).ok().flatten(),
|
||||
edited: row.get::<_, i64>(19).unwrap_or(0) > 0,
|
||||
reactions: Vec::new(),
|
||||
})
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -270,6 +270,21 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> {
|
|||
)?;
|
||||
}
|
||||
|
||||
let messages_exist: bool = conn
|
||||
.query_row(
|
||||
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages')",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if messages_exist {
|
||||
add_column_if_missing(
|
||||
conn,
|
||||
"key_version",
|
||||
"key_version INTEGER NOT NULL DEFAULT 1",
|
||||
)?;
|
||||
}
|
||||
|
||||
if current_version < 5 {
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
|
|
@ -814,6 +829,35 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> {
|
|||
)?;
|
||||
}
|
||||
|
||||
if current_version < 24 {
|
||||
let pending_relays_exist: bool = conn.query_row(
|
||||
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'pending_relays')",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
if pending_relays_exist {
|
||||
add_table_column_if_missing(
|
||||
conn,
|
||||
"pending_relays",
|
||||
"delivery_state",
|
||||
"delivery_state TEXT NOT NULL DEFAULT 'ready' CHECK (delivery_state IN ('ready', 'waiting_client', 'quarantined'))",
|
||||
)?;
|
||||
add_table_column_if_missing(conn, "pending_relays", "last_error", "last_error TEXT")?;
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
UPDATE pending_relays
|
||||
SET delivery_state = 'quarantined', last_error = 'target_recipient_mismatch'
|
||||
WHERE target_kind = 0
|
||||
AND relay_destination_user_id IS NOT NULL
|
||||
AND destination_id <> relay_destination_user_id;
|
||||
CREATE INDEX IF NOT EXISTS idx_pending_relays_active
|
||||
ON pending_relays (delivery_state, target_kind, destination_id, id);
|
||||
"#,
|
||||
)?;
|
||||
}
|
||||
conn.pragma_update(None, "user_version", 24)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -883,7 +927,7 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 23);
|
||||
assert_eq!(version, 24);
|
||||
for column in ["height", "reply_to", "edited_count", "deleted_by_external"] {
|
||||
let mut statement =
|
||||
conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?;
|
||||
|
|
@ -902,7 +946,7 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
run_migrations_on_connection(&conn)?;
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 23);
|
||||
assert_eq!(version, 24);
|
||||
for table in [
|
||||
"sync_heads",
|
||||
"sync_events",
|
||||
|
|
@ -926,7 +970,13 @@ mod tests {
|
|||
)?;
|
||||
assert_eq!(exists, 1);
|
||||
}
|
||||
for column in ["frame_id", "target_kind", "type_map_version"] {
|
||||
for column in [
|
||||
"frame_id",
|
||||
"target_kind",
|
||||
"type_map_version",
|
||||
"delivery_state",
|
||||
"last_error",
|
||||
] {
|
||||
let mut statement =
|
||||
conn.prepare("SELECT 1 FROM pragma_table_info('pending_relays') WHERE name = ?1")?;
|
||||
assert!(statement.exists([column])?);
|
||||
|
|
@ -942,7 +992,7 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 23);
|
||||
assert_eq!(version, 24);
|
||||
for column in [
|
||||
"id",
|
||||
"user_id",
|
||||
|
|
@ -1037,7 +1087,7 @@ mod tests {
|
|||
)?;
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(preserved, "remote_committed");
|
||||
assert_eq!(version, 23);
|
||||
assert_eq!(version, 24);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ pub struct ChatSecretQuery {
|
|||
pub user_id: String,
|
||||
pub chat_id: String,
|
||||
pub secret_id: Option<String>,
|
||||
pub version: Option<i64>,
|
||||
}
|
||||
|
||||
static E2EE_DB: LazyLock<Arc<Mutex<rusqlite::Connection>>> = LazyLock::new(|| {
|
||||
db::create_shared_connection(
|
||||
let database = db::create_shared_connection(
|
||||
"e2ee",
|
||||
r#"
|
||||
PRAGMA journal_mode = WAL;
|
||||
|
|
@ -44,7 +45,7 @@ static E2EE_DB: LazyLock<Arc<Mutex<rusqlite::Connection>>> = LazyLock::new(|| {
|
|||
wrapping_scheme TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, chat_id, secret_id)
|
||||
PRIMARY KEY (user_id, chat_id, secret_id, version)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_secrets_owner
|
||||
|
|
@ -52,37 +53,81 @@ static E2EE_DB: LazyLock<Arc<Mutex<rusqlite::Connection>>> = LazyLock::new(|| {
|
|||
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to create or initialize E2EE DB")
|
||||
.expect("Failed to create or initialize E2EE DB");
|
||||
if let Ok(connection) = database.lock() {
|
||||
let legacy = connection
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('chat_secrets') WHERE name = 'version' AND pk = 4",
|
||||
[],
|
||||
|row| row.get::<_, i64>(0),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0;
|
||||
if legacy {
|
||||
connection
|
||||
.execute_batch(
|
||||
"ALTER TABLE chat_secrets RENAME TO chat_secrets_legacy;
|
||||
CREATE TABLE chat_secrets (
|
||||
user_id TEXT NOT NULL, chat_id TEXT NOT NULL,
|
||||
secret_id TEXT NOT NULL, version INTEGER NOT NULL,
|
||||
encrypted_secret BLOB NOT NULL, kem_ciphertext BLOB NOT NULL,
|
||||
wrapping_scheme TEXT NOT NULL, created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, chat_id, secret_id, version)
|
||||
);
|
||||
INSERT INTO chat_secrets SELECT * FROM chat_secrets_legacy;
|
||||
DROP TABLE chat_secrets_legacy;",
|
||||
)
|
||||
.expect("Failed to migrate E2EE secret history schema");
|
||||
}
|
||||
}
|
||||
database
|
||||
});
|
||||
|
||||
pub fn put_chat_secret(record: StoredChatSecret) -> Result<(), StorageError> {
|
||||
db::with_conn(&E2EE_DB, |conn| {
|
||||
conn.execute(
|
||||
r#"
|
||||
INSERT INTO chat_secrets (
|
||||
user_id, chat_id, secret_id, version, encrypted_secret,
|
||||
kem_ciphertext, wrapping_scheme, created_at, updated_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
ON CONFLICT(user_id, chat_id, secret_id) DO UPDATE SET
|
||||
version = excluded.version,
|
||||
encrypted_secret = excluded.encrypted_secret,
|
||||
kem_ciphertext = excluded.kem_ciphertext,
|
||||
wrapping_scheme = excluded.wrapping_scheme,
|
||||
created_at = excluded.created_at,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
params![
|
||||
record.user_id,
|
||||
record.chat_id,
|
||||
record.secret_id,
|
||||
record.version,
|
||||
record.encrypted_secret,
|
||||
record.kem_ciphertext,
|
||||
record.wrapping_scheme,
|
||||
record.created_at,
|
||||
record.updated_at,
|
||||
],
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let existing = tx.query_row(
|
||||
"SELECT encrypted_secret, kem_ciphertext, wrapping_scheme FROM chat_secrets WHERE user_id = ?1 AND chat_id = ?2 AND secret_id = ?3 AND version = ?4",
|
||||
params![&record.user_id, &record.chat_id, &record.secret_id, record.version],
|
||||
|row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?, row.get::<_, String>(2)?)),
|
||||
).optional()?;
|
||||
if let Some(existing) = existing {
|
||||
if existing
|
||||
!= (
|
||||
record.encrypted_secret.clone(),
|
||||
record.kem_ciphertext.clone(),
|
||||
record.wrapping_scheme.clone(),
|
||||
)
|
||||
{
|
||||
return Err(rusqlite::Error::InvalidParameterName(
|
||||
"immutable chat secret version conflict".into(),
|
||||
));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let latest = tx.query_row(
|
||||
"SELECT MAX(version) FROM chat_secrets WHERE user_id = ?1 AND chat_id = ?2 AND secret_id = ?3",
|
||||
params![&record.user_id, &record.chat_id, &record.secret_id],
|
||||
|row| row.get::<_, Option<i64>>(0),
|
||||
)?;
|
||||
let expected = match latest {
|
||||
None => 1,
|
||||
Some(version) => version.checked_add(1).ok_or_else(|| {
|
||||
rusqlite::Error::InvalidParameterName("chat secret version overflow".into())
|
||||
})?,
|
||||
};
|
||||
if record.version != expected {
|
||||
return Err(rusqlite::Error::InvalidParameterName(format!(
|
||||
"chat secret version is out of sequence: expected {expected}, got {}",
|
||||
record.version
|
||||
)));
|
||||
}
|
||||
tx.execute(
|
||||
"INSERT INTO chat_secrets (user_id, chat_id, secret_id, version, encrypted_secret, kem_ciphertext, wrapping_scheme, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||
params![record.user_id, record.chat_id, record.secret_id, record.version, record.encrypted_secret, record.kem_ciphertext, record.wrapping_scheme, record.created_at, record.updated_at],
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
@ -117,10 +162,11 @@ pub fn get_chat_secret(query: ChatSecretQuery) -> Result<Option<StoredChatSecret
|
|||
WHERE user_id = ?1
|
||||
AND chat_id = ?2
|
||||
AND (?3 IS NULL OR secret_id = ?3)
|
||||
ORDER BY updated_at DESC
|
||||
AND (?4 IS NULL OR version = ?4)
|
||||
ORDER BY version DESC, updated_at DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
params![query.user_id, query.chat_id, query.secret_id],
|
||||
params![query.user_id, query.chat_id, query.secret_id, query.version],
|
||||
chat_secret_from_row,
|
||||
)
|
||||
.optional()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,26 @@ pub struct PendingRelay {
|
|||
pub relay_signer_id: Option<i64>,
|
||||
pub relay_destination_user_id: Option<i64>,
|
||||
pub relay_message_id: Option<String>,
|
||||
pub delivery_state: PendingRelayDeliveryState,
|
||||
pub last_error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PendingRelayDeliveryState {
|
||||
Ready,
|
||||
WaitingClient,
|
||||
Quarantined,
|
||||
}
|
||||
|
||||
impl PendingRelayDeliveryState {
|
||||
fn from_db(value: String) -> Result<Self, rusqlite::Error> {
|
||||
match value.as_str() {
|
||||
"ready" => Ok(Self::Ready),
|
||||
"waiting_client" => Ok(Self::WaitingClient),
|
||||
"quarantined" => Ok(Self::Quarantined),
|
||||
_ => Err(rusqlite::Error::InvalidQuery),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -31,6 +51,15 @@ pub fn enqueue(
|
|||
frame_id: u32,
|
||||
type_map_version: &str,
|
||||
) -> Result<(), StorageError> {
|
||||
if let RouteTarget::User(destination) = target {
|
||||
let destination = i64::try_from(destination)
|
||||
.map_err(|_| StorageError::Other("relay destination ID exceeds SQLite range".into()))?;
|
||||
if destination != relay.destination_user_id {
|
||||
return Err(StorageError::Other(
|
||||
"user relay target does not match final recipient".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
db::with_immediate_transaction(|tx| {
|
||||
enqueue_in_tx(
|
||||
tx,
|
||||
|
|
@ -79,7 +108,7 @@ pub fn enqueue_in_tx(
|
|||
pub fn list(limit: i64) -> Result<Vec<PendingRelay>, StorageError> {
|
||||
db::with_db(|connection| {
|
||||
let mut statement = connection.prepare(
|
||||
"SELECT id, destination_id, target_kind, frame, created_at, frame_id, type_map_version, relay_signer_id, relay_destination_user_id, relay_message_id FROM pending_relays ORDER BY id LIMIT ?1",
|
||||
"SELECT id, destination_id, target_kind, frame, created_at, frame_id, type_map_version, relay_signer_id, relay_destination_user_id, relay_message_id, delivery_state, last_error FROM pending_relays ORDER BY id LIMIT ?1",
|
||||
)?;
|
||||
let rows = statement.query_map(params![limit.clamp(1, 500)], |row| {
|
||||
let destination_id = row.get::<_, i64>(1)?;
|
||||
|
|
@ -118,12 +147,26 @@ pub fn list(limit: i64) -> Result<Vec<PendingRelay>, StorageError> {
|
|||
relay_signer_id: row.get(7)?,
|
||||
relay_destination_user_id: row.get(8)?,
|
||||
relay_message_id: row.get(9)?,
|
||||
delivery_state: PendingRelayDeliveryState::from_db(row.get(10)?)?,
|
||||
last_error: row.get(11)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
/* Select only relays whose target and signer remain locally managed. This is
|
||||
* the delivery-authority check after user release, independent of row state. */
|
||||
pub fn list_active(limit: i64) -> Result<Vec<PendingRelay>, StorageError> {
|
||||
db::with_db(|connection| {
|
||||
let mut statement = connection.prepare(
|
||||
"SELECT p.id, p.destination_id, p.target_kind, p.frame, p.created_at, p.frame_id, p.type_map_version, p.relay_signer_id, p.relay_destination_user_id, p.relay_message_id, p.delivery_state, p.last_error FROM pending_relays p WHERE p.delivery_state = 'ready' AND ((p.target_kind = 0 AND EXISTS (SELECT 1 FROM users u WHERE u.user_id = p.destination_id)) OR (p.target_kind = 1 AND EXISTS (SELECT 1 FROM users u WHERE u.user_id = p.relay_signer_id))) ORDER BY p.id LIMIT ?1",
|
||||
)?;
|
||||
let rows = statement.query_map(params![limit.clamp(1, 500)], pending_relay_from_row)?;
|
||||
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_without_relay_identity() -> Result<Vec<PendingRelay>, StorageError> {
|
||||
list_without_relay_identity_after(0, i64::MAX)
|
||||
}
|
||||
|
|
@ -133,7 +176,7 @@ pub fn list_without_relay_identity_after(
|
|||
limit: i64,
|
||||
) -> Result<Vec<PendingRelay>, StorageError> {
|
||||
db::with_db(|connection| {
|
||||
let mut statement = connection.prepare("SELECT id, destination_id, target_kind, frame, created_at, frame_id, type_map_version, relay_signer_id, relay_destination_user_id, relay_message_id FROM pending_relays WHERE id > ?1 AND (relay_signer_id IS NULL OR relay_destination_user_id IS NULL OR relay_message_id IS NULL) ORDER BY id LIMIT ?2")?;
|
||||
let mut statement = connection.prepare("SELECT id, destination_id, target_kind, frame, created_at, frame_id, type_map_version, relay_signer_id, relay_destination_user_id, relay_message_id, delivery_state, last_error FROM pending_relays WHERE id > ?1 AND (relay_signer_id IS NULL OR relay_destination_user_id IS NULL OR relay_message_id IS NULL) ORDER BY id LIMIT ?2")?;
|
||||
let rows = statement.query_map(
|
||||
params![after_id, limit.clamp(1, 500)],
|
||||
pending_relay_from_row,
|
||||
|
|
@ -161,11 +204,68 @@ fn pending_relay_from_row(row: &rusqlite::Row<'_>) -> Result<PendingRelay, rusql
|
|||
relay_signer_id: row.get(7)?,
|
||||
relay_destination_user_id: row.get(8)?,
|
||||
relay_message_id: row.get(9)?,
|
||||
delivery_state: PendingRelayDeliveryState::from_db(row.get(10)?)?,
|
||||
last_error: row.get(11)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mark_client_offline(destination_user_id: i64, frame_id: u32) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute("UPDATE pending_relays SET delivery_state = 'waiting_client', last_error = 'client_offline' WHERE target_kind = 0 AND destination_id = ?1 AND frame_id = ?2", params![destination_user_id, i64::from(frame_id)])?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pause_client_deliveries(destination_user_id: i64) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
"UPDATE pending_relays SET delivery_state = 'waiting_client', last_error = 'awaiting_state_ack' WHERE target_kind = 0 AND destination_id = ?1 AND delivery_state = 'ready'",
|
||||
[destination_user_id],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resume_client_deliveries(destination_user_id: i64) -> Result<usize, StorageError> {
|
||||
db::with_db(|connection| {
|
||||
Ok(connection.execute("UPDATE pending_relays SET delivery_state = 'ready', last_error = NULL WHERE target_kind = 0 AND destination_id = ?1 AND delivery_state = 'waiting_client'", [destination_user_id])?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resume_managed_client_deliveries() -> Result<usize, StorageError> {
|
||||
db::with_db(|connection| {
|
||||
Ok(connection.execute("UPDATE pending_relays SET delivery_state = 'ready', last_error = NULL WHERE target_kind = 0 AND delivery_state = 'waiting_client' AND EXISTS (SELECT 1 FROM users u WHERE u.user_id = pending_relays.destination_id)", [])?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn quarantine_target_mismatch(id: i64) -> Result<(), StorageError> {
|
||||
db::with_db(|connection| {
|
||||
connection.execute("UPDATE pending_relays SET delivery_state = 'quarantined', last_error = 'target_recipient_mismatch' WHERE id = ?1", [id])?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_relay_identity(id: i64, relay: &RelayIdentity) -> Result<(), StorageError> {
|
||||
db::with_db(|connection| {
|
||||
let target_kind = connection.query_row(
|
||||
"SELECT target_kind FROM pending_relays WHERE id = ?1",
|
||||
[id],
|
||||
|row| row.get::<_, i64>(0),
|
||||
)?;
|
||||
if target_kind == 0 {
|
||||
let destination_id = connection.query_row(
|
||||
"SELECT destination_id FROM pending_relays WHERE id = ?1",
|
||||
[id],
|
||||
|row| row.get::<_, i64>(0),
|
||||
)?;
|
||||
if destination_id != relay.destination_user_id {
|
||||
connection.execute(
|
||||
"UPDATE pending_relays SET delivery_state = 'quarantined', last_error = 'target_recipient_mismatch' WHERE id = ?1",
|
||||
[id],
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
connection.execute("UPDATE pending_relays SET relay_signer_id = ?2, relay_destination_user_id = ?3, relay_message_id = ?4 WHERE id = ?1", params![id, relay.signer_id, relay.destination_user_id, relay.message_id])?;
|
||||
Ok(())
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue