diff --git a/rust/.sqlx/query-0134b965d7cbf9feda5bfc93bc410d91414a44251857ba9b462c99ff88c104f3.json b/rust/.sqlx/query-0134b965d7cbf9feda5bfc93bc410d91414a44251857ba9b462c99ff88c104f3.json new file mode 100644 index 00000000..83f3c31e --- /dev/null +++ b/rust/.sqlx/query-0134b965d7cbf9feda5bfc93bc410d91414a44251857ba9b462c99ff88c104f3.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM signal_session_resets", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "0134b965d7cbf9feda5bfc93bc410d91414a44251857ba9b462c99ff88c104f3" +} diff --git a/rust/.sqlx/query-06fdf3188c162f0cc9240c17506c3f2afc80d819583d005471e2c9968dca85e2.json b/rust/.sqlx/query-06fdf3188c162f0cc9240c17506c3f2afc80d819583d005471e2c9968dca85e2.json new file mode 100644 index 00000000..73019869 --- /dev/null +++ b/rust/.sqlx/query-06fdf3188c162f0cc9240c17506c3f2afc80d819583d005471e2c9968dca85e2.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT group_id FROM groups ORDER BY total_media_counter DESC LIMIT 1", + "describe": { + "columns": [ + { + "name": "group_id", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "groups", + "name": "group_id" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "06fdf3188c162f0cc9240c17506c3f2afc80d819583d005471e2c9968dca85e2" +} diff --git a/rust/.sqlx/query-141c8905f5d1f47fdbd88a007115dda16edadf2e6f92e7b190e99925870bdc18.json b/rust/.sqlx/query-141c8905f5d1f47fdbd88a007115dda16edadf2e6f92e7b190e99925870bdc18.json deleted file mode 100644 index f09b4042..00000000 --- a/rust/.sqlx/query-141c8905f5d1f47fdbd88a007115dda16edadf2e6f92e7b190e99925870bdc18.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT OR REPLACE INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt, wake_receiver)\n VALUES (?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "141c8905f5d1f47fdbd88a007115dda16edadf2e6f92e7b190e99925870bdc18" -} diff --git a/rust/.sqlx/query-1d5f5de6b4d541014f651aa00a2fc91cf407fef2c34a5248fca3f02dc0a4c06e.json b/rust/.sqlx/query-1d5f5de6b4d541014f651aa00a2fc91cf407fef2c34a5248fca3f02dc0a4c06e.json new file mode 100644 index 00000000..48b0a6a3 --- /dev/null +++ b/rust/.sqlx/query-1d5f5de6b4d541014f651aa00a2fc91cf407fef2c34a5248fca3f02dc0a4c06e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_identities(name, identity_key, timestamp)\n VALUES('restored-peer', x'040506', 1)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "1d5f5de6b4d541014f651aa00a2fc91cf407fef2c34a5248fca3f02dc0a4c06e" +} diff --git a/rust/.sqlx/query-34ad29e5771ec73110b509a063ff69e5f4b349c2efab46a250a1952ea4e7ade5.json b/rust/.sqlx/query-34ad29e5771ec73110b509a063ff69e5f4b349c2efab46a250a1952ea4e7ade5.json new file mode 100644 index 00000000..c38b7602 --- /dev/null +++ b/rust/.sqlx/query-34ad29e5771ec73110b509a063ff69e5f4b349c2efab46a250a1952ea4e7ade5.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) AS \"count!: i64\" FROM signal_sessions", + "describe": { + "columns": [ + { + "name": "count!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "34ad29e5771ec73110b509a063ff69e5f4b349c2efab46a250a1952ea4e7ade5" +} diff --git a/rust/.sqlx/query-36b3701a17e020cc774c15ea84267e5f99d53ac3aca4adb323697cb87e683b1f.json b/rust/.sqlx/query-36b3701a17e020cc774c15ea84267e5f99d53ac3aca4adb323697cb87e683b1f.json new file mode 100644 index 00000000..d37b0e75 --- /dev/null +++ b/rust/.sqlx/query-36b3701a17e020cc774c15ea84267e5f99d53ac3aca4adb323697cb87e683b1f.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO receipts(receipt_id, contact_id, message, contact_will_sends_receipt, wake_receiver)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(receipt_id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 5 + }, + "nullable": [] + }, + "hash": "36b3701a17e020cc774c15ea84267e5f99d53ac3aca4adb323697cb87e683b1f" +} diff --git a/rust/.sqlx/query-420d5c24bbac17a8f4db4088515782c5576ea708c9168ea2d05babd8c5a175f4.json b/rust/.sqlx/query-420d5c24bbac17a8f4db4088515782c5576ea708c9168ea2d05babd8c5a175f4.json new file mode 100644 index 00000000..ac6d02e5 --- /dev/null +++ b/rust/.sqlx/query-420d5c24bbac17a8f4db4088515782c5576ea708c9168ea2d05babd8c5a175f4.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO legacy_upgrade_requests(contact_id, requested_at)\n VALUES (?, ?)\n ON CONFLICT(contact_id) DO UPDATE SET requested_at = excluded.requested_at\n WHERE legacy_upgrade_requests.requested_at <= ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "420d5c24bbac17a8f4db4088515782c5576ea708c9168ea2d05babd8c5a175f4" +} diff --git a/rust/.sqlx/query-43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed.json b/rust/.sqlx/query-43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed.json new file mode 100644 index 00000000..760cefbf --- /dev/null +++ b/rust/.sqlx/query-43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM signal_sessions", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed" +} diff --git a/rust/.sqlx/query-74323299e0c048f9395ead9775a1e4b61fcc935db89378e80f30344b8c115a81.json b/rust/.sqlx/query-74323299e0c048f9395ead9775a1e4b61fcc935db89378e80f30344b8c115a81.json new file mode 100644 index 00000000..8ce96c67 --- /dev/null +++ b/rust/.sqlx/query-74323299e0c048f9395ead9775a1e4b61fcc935db89378e80f30344b8c115a81.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_sessions(name, device_id, record_bytes)\n VALUES('restored-peer', 1, x'010203')\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "74323299e0c048f9395ead9775a1e4b61fcc935db89378e80f30344b8c115a81" +} diff --git a/rust/.sqlx/query-74e4937df433524718139d6868c4fc474939148de0f885103e7a4f39b241b553.json b/rust/.sqlx/query-74e4937df433524718139d6868c4fc474939148de0f885103e7a4f39b241b553.json new file mode 100644 index 00000000..09067c96 --- /dev/null +++ b/rust/.sqlx/query-74e4937df433524718139d6868c4fc474939148de0f885103e7a4f39b241b553.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT identity_key FROM signal_identities WHERE name = 'restored-peer'", + "describe": { + "columns": [ + { + "name": "identity_key", + "ordinal": 0, + "type_info": "Blob", + "origin": { + "Table": { + "table": "signal_identities", + "name": "identity_key" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "74e4937df433524718139d6868c4fc474939148de0f885103e7a4f39b241b553" +} diff --git a/rust/.sqlx/query-75f560bc922045cfacf0b5ef1f7772372cea08954859dc64de961c715ce4c55a.json b/rust/.sqlx/query-75f560bc922045cfacf0b5ef1f7772372cea08954859dc64de961c715ce4c55a.json new file mode 100644 index 00000000..9013fb59 --- /dev/null +++ b/rust/.sqlx/query-75f560bc922045cfacf0b5ef1f7772372cea08954859dc64de961c715ce4c55a.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) AS \"count!: i64\" FROM signal_session_resets", + "describe": { + "columns": [ + { + "name": "count!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "75f560bc922045cfacf0b5ef1f7772372cea08954859dc64de961c715ce4c55a" +} diff --git a/rust/.sqlx/query-83913e710fbd04e98cd6de21e7c917ed6472ef90230a10a69f74cb07d31a99fa.json b/rust/.sqlx/query-83913e710fbd04e98cd6de21e7c917ed6472ef90230a10a69f74cb07d31a99fa.json deleted file mode 100644 index e9f6ed5c..00000000 --- a/rust/.sqlx/query-83913e710fbd04e98cd6de21e7c917ed6472ef90230a10a69f74cb07d31a99fa.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "DELETE FROM receipts WHERE contact_id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "83913e710fbd04e98cd6de21e7c917ed6472ef90230a10a69f74cb07d31a99fa" -} diff --git a/rust/.sqlx/query-a40ea0dd25f0a1909a08aa2e1db6c5f360646a931a7e747047364a81f1e67ab5.json b/rust/.sqlx/query-a40ea0dd25f0a1909a08aa2e1db6c5f360646a931a7e747047364a81f1e67ab5.json new file mode 100644 index 00000000..21091630 --- /dev/null +++ b/rust/.sqlx/query-a40ea0dd25f0a1909a08aa2e1db6c5f360646a931a7e747047364a81f1e67ab5.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE groups SET\n flame_counter = ?,\n last_flame_counter_change = ?,\n last_message_send = ?,\n last_message_received = ?\n WHERE group_id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 5 + }, + "nullable": [] + }, + "hash": "a40ea0dd25f0a1909a08aa2e1db6c5f360646a931a7e747047364a81f1e67ab5" +} diff --git a/rust/.sqlx/query-b7c5107cc99685fcb27557600950b1abcb5af657b66319a01fbe7512341bdf6b.json b/rust/.sqlx/query-b7c5107cc99685fcb27557600950b1abcb5af657b66319a01fbe7512341bdf6b.json new file mode 100644 index 00000000..9414a0a3 --- /dev/null +++ b/rust/.sqlx/query-b7c5107cc99685fcb27557600950b1abcb5af657b66319a01fbe7512341bdf6b.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT max_flame_counter, max_flame_counter_from FROM groups WHERE group_id = ?", + "describe": { + "columns": [ + { + "name": "max_flame_counter", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "max_flame_counter" + } + } + }, + { + "name": "max_flame_counter_from", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "max_flame_counter_from" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "b7c5107cc99685fcb27557600950b1abcb5af657b66319a01fbe7512341bdf6b" +} diff --git a/rust/.sqlx/query-cb70d13126eded79712bbbfed1ae0d13c4857c1c2442be753d5ba34bf96aceb5.json b/rust/.sqlx/query-cb70d13126eded79712bbbfed1ae0d13c4857c1c2442be753d5ba34bf96aceb5.json new file mode 100644 index 00000000..9f9db91b --- /dev/null +++ b/rust/.sqlx/query-cb70d13126eded79712bbbfed1ae0d13c4857c1c2442be753d5ba34bf96aceb5.json @@ -0,0 +1,86 @@ +{ + "db_name": "SQLite", + "query": "SELECT flame_counter, max_flame_counter, max_flame_counter_from,\n last_message_send, last_message_received, last_flame_counter_change\n FROM groups WHERE group_id = ?", + "describe": { + "columns": [ + { + "name": "flame_counter", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "flame_counter" + } + } + }, + { + "name": "max_flame_counter", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "max_flame_counter" + } + } + }, + { + "name": "max_flame_counter_from", + "ordinal": 2, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "max_flame_counter_from" + } + } + }, + { + "name": "last_message_send", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_message_send" + } + } + }, + { + "name": "last_message_received", + "ordinal": 4, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_message_received" + } + } + }, + { + "name": "last_flame_counter_change", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_flame_counter_change" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true, + true, + true, + true + ] + }, + "hash": "cb70d13126eded79712bbbfed1ae0d13c4857c1c2442be753d5ba34bf96aceb5" +} diff --git a/rust/.sqlx/query-db5192b62d8a4f9f23ad7befff1b9e8e463134a2cf90363a4a98d6b5b37fc723.json b/rust/.sqlx/query-db5192b62d8a4f9f23ad7befff1b9e8e463134a2cf90363a4a98d6b5b37fc723.json new file mode 100644 index 00000000..66df7474 --- /dev/null +++ b/rust/.sqlx/query-db5192b62d8a4f9f23ad7befff1b9e8e463134a2cf90363a4a98d6b5b37fc723.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO signal_session_resets(\n name, device_id, last_reset_at, window_started_at, resets_in_window\n ) VALUES('restored-peer', 1, 1, 1, 1)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "db5192b62d8a4f9f23ad7befff1b9e8e463134a2cf90363a4a98d6b5b37fc723" +} diff --git a/rust/.sqlx/query-df4c2b8e3b106c3f91e4d816627c7985c9f56fa459c16357d727fc292d226529.json b/rust/.sqlx/query-df4c2b8e3b106c3f91e4d816627c7985c9f56fa459c16357d727fc292d226529.json new file mode 100644 index 00000000..6a5b45dd --- /dev/null +++ b/rust/.sqlx/query-df4c2b8e3b106c3f91e4d816627c7985c9f56fa459c16357d727fc292d226529.json @@ -0,0 +1,86 @@ +{ + "db_name": "SQLite", + "query": "SELECT group_id, flame_counter, also_best_friend,\n last_message_send, last_message_received, last_flame_counter_change\n FROM groups", + "describe": { + "columns": [ + { + "name": "group_id", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "groups", + "name": "group_id" + } + } + }, + { + "name": "flame_counter", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "flame_counter" + } + } + }, + { + "name": "also_best_friend", + "ordinal": 2, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "also_best_friend" + } + } + }, + { + "name": "last_message_send", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_message_send" + } + } + }, + { + "name": "last_message_received", + "ordinal": 4, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_message_received" + } + } + }, + { + "name": "last_flame_counter_change", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "groups", + "name": "last_flame_counter_change" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + true, + true, + true + ] + }, + "hash": "df4c2b8e3b106c3f91e4d816627c7985c9f56fa459c16357d727fc292d226529" +} diff --git a/rust/.sqlx/query-10facb63e1057c6acfe8b60cfe5f6deb3535eed60977d917f57cae97bb220e30.json b/rust/.sqlx/query-fef6c2ffbb80a5aa1e9cc70e3971cd9dfe0f828b984e18dff15c77389165b27c.json similarity index 71% rename from rust/.sqlx/query-10facb63e1057c6acfe8b60cfe5f6deb3535eed60977d917f57cae97bb220e30.json rename to rust/.sqlx/query-fef6c2ffbb80a5aa1e9cc70e3971cd9dfe0f828b984e18dff15c77389165b27c.json index fc127651..cb0f7f52 100644 --- a/rust/.sqlx/query-10facb63e1057c6acfe8b60cfe5f6deb3535eed60977d917f57cae97bb220e30.json +++ b/rust/.sqlx/query-fef6c2ffbb80a5aa1e9cc70e3971cd9dfe0f828b984e18dff15c77389165b27c.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT group_id, total_media_counter, last_flame_counter_change,\n last_flame_sync, flame_counter\n FROM groups WHERE last_flame_counter_change IS NOT NULL", + "query": "SELECT group_id, total_media_counter, last_flame_counter_change, last_flame_sync\n FROM groups WHERE last_flame_counter_change IS NOT NULL", "describe": { "columns": [ { @@ -46,17 +46,6 @@ "name": "last_flame_sync" } } - }, - { - "name": "flame_counter", - "ordinal": 4, - "type_info": "Integer", - "origin": { - "Table": { - "table": "groups", - "name": "flame_counter" - } - } } ], "parameters": { @@ -66,9 +55,8 @@ false, false, true, - true, - false + true ] }, - "hash": "10facb63e1057c6acfe8b60cfe5f6deb3535eed60977d917f57cae97bb220e30" + "hash": "fef6c2ffbb80a5aa1e9cc70e3971cd9dfe0f828b984e18dff15c77389165b27c" } diff --git a/rust/src/api/messages/incoming.rs b/rust/src/api/messages/incoming.rs index 344d16a1..a4baf703 100644 --- a/rust/src/api/messages/incoming.rs +++ b/rust/src/api/messages/incoming.rs @@ -297,8 +297,9 @@ async fn reset_unusable_session( } /// Rebuilds a v2 Signal session for a peer that still speaks the legacy -/// protocol, so the decryption error queued for the message can be answered -/// with a session the peer can upgrade to. +/// protocol, so what we send them next already goes out as v2. The decryption +/// error answering their legacy message is plaintext and does not use it; see +/// [`claim_legacy_upgrade_request`] for how the peer itself is moved to v2. /// /// Runs before the inbound transaction opens: it awaits a server round-trip and /// `establish_signal_session` writes through the pool, and the app database @@ -347,6 +348,43 @@ async fn upgrade_legacy_session_to_v2( Ok(()) } +/// How long a legacy peer is given to move to v2 before it is asked again. +const LEGACY_UPGRADE_REQUEST_INTERVAL_SECS: i64 = 60 * 60; + +/// Claims the right to ask a legacy peer to move to v2, at most once per +/// [`LEGACY_UPGRADE_REQUEST_INTERVAL_SECS`]. +/// +/// Clients before 0.6.0 fetch a fresh prekey bundle only for a +/// `PREKEY_UNKNOWN` decryption error, and that bundle is what switches the +/// contact to v2 on their side. They answer every decryption error by resending +/// the same queued message, still encrypted as v1, so answering each of those +/// resends would never end and would spend one of our one-time prekeys per +/// round. Leaving the resends unanswered stops the loop: without a reply the +/// old client does not send the message again. +async fn claim_legacy_upgrade_request( + t: &mut Transaction<'_, Sqlite>, + contact_id: i64, +) -> Result { + let now = crate::utils::current_time().timestamp(); + let asked_before = now - LEGACY_UPGRADE_REQUEST_INTERVAL_SECS; + let claimed = sqlx::query!( + r#" + INSERT INTO legacy_upgrade_requests(contact_id, requested_at) + VALUES (?, ?) + ON CONFLICT(contact_id) DO UPDATE SET requested_at = excluded.requested_at + WHERE legacy_upgrade_requests.requested_at <= ? + "#, + contact_id, + now, + asked_before, + ) + .execute(&mut **t) + .await? + .rows_affected() + > 0; + Ok(claimed) +} + #[tracing::instrument( skip_all, fields( @@ -466,7 +504,22 @@ pub async fn handle_decoded_server_message( handle_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?; } Type::Ciphertext | Type::PrekeyBundle => { - queue_decryption_error(&mut t, from_user_id, &message.receipt_id, 0).await?; + if claim_legacy_upgrade_request(&mut t, from_user_id).await? { + tracing::info!("Asking the legacy peer to fetch our v2 prekey bundle"); + queue_decryption_error( + &mut t, + from_user_id, + &message.receipt_id, + DecryptionErrorType::PrekeyUnknown as i32, + ) + .await?; + } else { + tracing::info!( + "Leaving a legacy message unanswered; the peer was already asked to upgrade" + ); + } + // Never acknowledged: the message was not read, so the sender must + // not be told it was delivered. sends_error_response = true; } Type::CiphertextV2 => { @@ -913,4 +966,37 @@ mod tests { .unwrap_or_else(PoisonError::into_inner) .contains_key(receipt_id)); } + + /// An old client resends the same v1 message for every decryption error, + /// so only the first one in an interval may be answered. + #[tokio::test] + async fn a_legacy_peer_is_asked_to_upgrade_once_per_interval() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("app.sqlite"); + let database = crate::database::app::AppDatabase::new(path.to_str().unwrap(), None, false) + .await + .unwrap(); + database.run_migrations().await.unwrap(); + sqlx::query("INSERT INTO contacts(user_id, username) VALUES (7, 'alice')") + .execute(&database.pool) + .await + .unwrap(); + + let mut t = database.pool.begin().await.unwrap(); + assert!(claim_legacy_upgrade_request(&mut t, 7).await.unwrap()); + assert!(!claim_legacy_upgrade_request(&mut t, 7).await.unwrap()); + t.commit().await.unwrap(); + + // Once the interval has passed without the peer upgrading, it is asked + // again, for example because its bundle fetch failed. + sqlx::query("UPDATE legacy_upgrade_requests SET requested_at = requested_at - ?") + .bind(LEGACY_UPGRADE_REQUEST_INTERVAL_SECS) + .execute(&database.pool) + .await + .unwrap(); + let mut t = database.pool.begin().await.unwrap(); + assert!(claim_legacy_upgrade_request(&mut t, 7).await.unwrap()); + assert!(!claim_legacy_upgrade_request(&mut t, 7).await.unwrap()); + t.commit().await.unwrap(); + } } diff --git a/rust/src/database/app/migrations/0024_legacy_upgrade_requests.sql b/rust/src/database/app/migrations/0024_legacy_upgrade_requests.sql new file mode 100644 index 00000000..830b319b --- /dev/null +++ b/rust/src/database/app/migrations/0024_legacy_upgrade_requests.sql @@ -0,0 +1,8 @@ +-- When a peer still sending legacy (v1) Signal messages was last asked to move +-- to v2. Older clients retry every decryption error with the same v1 +-- ciphertext, so the request is answered once per interval and the retries in +-- between are left unanswered, which is what ends that loop. +CREATE TABLE legacy_upgrade_requests ( + contact_id INTEGER PRIMARY KEY REFERENCES contacts(user_id) ON DELETE CASCADE, + requested_at INTEGER NOT NULL +);