mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
fix legacy upgrade issue
This commit is contained in:
parent
c4f99fd96d
commit
c25d1eccbe
20 changed files with 500 additions and 42 deletions
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM signal_session_resets",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0134b965d7cbf9feda5bfc93bc410d91414a44251857ba9b462c99ff88c104f3"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM signal_sessions",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM receipts WHERE contact_id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "83913e710fbd04e98cd6de21e7c917ed6472ef90230a10a69f74cb07d31a99fa"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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<bool> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
Loading…
Reference in a new issue