diff --git a/.changeset/fix-backup-import-partial.md b/.changeset/fix-backup-import-partial.md new file mode 100644 index 000000000..2e856b53a --- /dev/null +++ b/.changeset/fix-backup-import-partial.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix restoring from key backup failing entirely when a single backed-up key could not be read; the rest of the keys are now restored. diff --git a/.changeset/fix-push-anr.md b/.changeset/fix-push-anr.md new file mode 100644 index 000000000..c73d34892 --- /dev/null +++ b/.changeset/fix-push-anr.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix "Sable isn't responding" appearing repeatedly on Android when notifications arrived while the app was in the background. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 56e00a70e..9bd25aa93 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8325,7 +8325,7 @@ dependencies = [ [[package]] name = "tauri-plugin-notifications" version = "0.5.0" -source = "git+https://github.com/SableClient/tauri-plugin-notifications.git?rev=d06bc72dae599174ffa6d57c72c3a0d652346507#d06bc72dae599174ffa6d57c72c3a0d652346507" +source = "git+https://github.com/SableClient/tauri-plugin-notifications.git?rev=628f99df7a7634eed423388392ad995f13e2d08c#628f99df7a7634eed423388392ad995f13e2d08c" dependencies = [ "log", "notify-rust", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5f189e059..f26aa20d9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -109,12 +109,12 @@ windows = { version = "0.62", features = [ tauri-plugin-single-instance = { version = "2.4.3", features = ["deep-link"] } [target.'cfg(any(windows, target_os = "linux"))'.dependencies] -tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "d06bc72dae599174ffa6d57c72c3a0d652346507" } +tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "628f99df7a7634eed423388392ad995f13e2d08c" } # default-features = false drops notify-rust so macOS uses the native # UNUserNotificationCenter backend (needs a signed .app to deliver). [target.'cfg(target_os = "macos")'.dependencies] -tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "d06bc72dae599174ffa6d57c72c3a0d652346507", default-features = false } +tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "628f99df7a7634eed423388392ad995f13e2d08c", default-features = false } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-updater = { version = "2", optional = true } @@ -138,7 +138,7 @@ libloading = "0.9" zbus = "5" [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] -tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "d06bc72dae599174ffa6d57c72c3a0d652346507", features = [ +tauri-plugin-notifications = { git = "https://github.com/SableClient/tauri-plugin-notifications.git", rev = "628f99df7a7634eed423388392ad995f13e2d08c", features = [ "push-notifications", ] } tauri-plugin-edge-to-edge = { git = "https://github.com/SableClient/tauri-plugin-edge-to-edge.git", rev = "33c6116c27be28c06df5a9d02231ecc5fdeb93c5" } diff --git a/src-tauri/src/matrix_crypto/backup.rs b/src-tauri/src/matrix_crypto/backup.rs index 57476a754..d406bf5b3 100644 --- a/src-tauri/src/matrix_crypto/backup.rs +++ b/src-tauri/src/matrix_crypto/backup.rs @@ -43,6 +43,29 @@ struct BackedUpSession { key: Value, } +fn exportable_keys(sessions: Vec) -> (Vec, usize) { + let mut exported = Vec::with_capacity(sessions.len()); + let mut skipped = 0usize; + + for session in sessions { + let Ok(room) = RoomId::parse(&session.room_id) else { + skipped += 1; + continue; + }; + let Ok(key) = serde_json::from_value::(session.key) else { + skipped += 1; + continue; + }; + exported.push(ExportedRoomKey::from_backed_up_room_key( + room, + session.session_id, + key, + )); + } + + (exported, skipped) +} + fn import_result(result: RoomKeyImportResult) -> Value { json!({ "importedCount": result.imported_count, @@ -152,17 +175,9 @@ async fn handle(machine: &OlmMachine, method: &str, args: &Value) -> Result 0 { + log::warn!("importBackedUpRoomKeys: skipped {skipped} unreadable backed-up keys"); } let result = machine @@ -254,4 +269,68 @@ mod tests { assert_eq!(parsed[0].room_id, "!room:example.org"); assert_eq!(parsed[0].session_id, "session-1"); } + + const VALID_CURVE_KEY: &str = "KyHFkVuB9MFbEkiCw+idNHKbiM8r3cWpNNPdyHkFeHY"; + + fn valid_session_key() -> String { + use matrix_sdk_crypto::vodozemac::{base64_encode, Ed25519SecretKey}; + + let signing_key = Ed25519SecretKey::new().public_key(); + let mut bytes = Vec::with_capacity(165); + bytes.push(1u8); + bytes.extend_from_slice(&0u32.to_be_bytes()); + bytes.extend_from_slice(&[0u8; 128]); + bytes.extend_from_slice(signing_key.as_bytes()); + base64_encode(bytes) + } + + fn session(room_id: &str, sender_key: &str, session_key: &str) -> BackedUpSession { + serde_json::from_value(json!({ + "room_id": room_id, + "session_id": "session-1", + "algorithm": "m.megolm.v1.aes-sha2", + "sender_key": sender_key, + "session_key": session_key, + "sender_claimed_keys": {}, + "forwarding_curve25519_key_chain": [], + })) + .unwrap() + } + + /// MSC3700 deprecated `sender_key`, so backups legitimately carry blank ones. + /// These used to abort the whole import with an error, costing the user every + /// other key in the batch; they must be counted and stepped over instead. + #[test] + fn a_readable_session_survives_a_bad_neighbour() { + let key = valid_session_key(); + let sessions = vec![ + session("!room:example.org", "", &key), + session("!room:example.org", VALID_CURVE_KEY, &key), + ]; + + let (exported, skipped) = exportable_keys(sessions); + + assert_eq!(skipped, 1); + assert_eq!(exported.len(), 1); + } + + #[test] + fn a_malformed_room_id_is_skipped_too() { + let key = valid_session_key(); + let (exported, skipped) = + exportable_keys(vec![session("not-a-room-id", VALID_CURVE_KEY, &key)]); + + assert!(exported.is_empty()); + assert_eq!(skipped, 1); + } + + #[test] + fn a_wholly_readable_batch_skips_nothing() { + let key = valid_session_key(); + let (exported, skipped) = + exportable_keys(vec![session("!room:example.org", VALID_CURVE_KEY, &key)]); + + assert_eq!(exported.len(), 1); + assert_eq!(skipped, 0); + } }