From 8894050176405d92aaae0ff46292c729a9ea763e Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Fri, 14 Nov 2025 17:04:03 -0800 Subject: [PATCH] chat: Fix parsing of 409/410 responses to sendMultiRecipientMessage --- RELEASE_NOTES.md | 1 + .../net/UnauthMessagesServiceTest.kt | 10 +++-- .../ts/test/chat/UnauthMessagesServiceTest.ts | 10 +++-- rust/net/chat/src/ws/messages.rs | 40 ++++++++++++++----- .../UnauthMessagesServiceTests.swift | 10 +++-- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5f920ca17..ba6e2f250 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,3 @@ v0.86.5 +- chat: Fixed parsing of 409/410 responses for sendMultiRecipientMessage. diff --git a/java/client/src/test/java/org/signal/libsignal/net/UnauthMessagesServiceTest.kt b/java/client/src/test/java/org/signal/libsignal/net/UnauthMessagesServiceTest.kt index c04b3b2c0..70d747fb7 100644 --- a/java/client/src/test/java/org/signal/libsignal/net/UnauthMessagesServiceTest.kt +++ b/java/client/src/test/java/org/signal/libsignal/net/UnauthMessagesServiceTest.kt @@ -123,8 +123,10 @@ class UnauthMessagesServiceTest { [ { "uuid": "$uuid", - "missingDevices": [4, 5], - "extraDevices": [40, 50] + "devices": { + "missingDevices": [4, 5], + "extraDevices": [40, 50] + } } ] """.trimIndent() @@ -171,7 +173,9 @@ class UnauthMessagesServiceTest { [ { "uuid": "$uuid", - "staleDevices": [4, 5] + "devices": { + "staleDevices": [4, 5] + } } ] """.trimIndent() diff --git a/node/ts/test/chat/UnauthMessagesServiceTest.ts b/node/ts/test/chat/UnauthMessagesServiceTest.ts index 8acfaafbe..be734086d 100644 --- a/node/ts/test/chat/UnauthMessagesServiceTest.ts +++ b/node/ts/test/chat/UnauthMessagesServiceTest.ts @@ -124,8 +124,10 @@ describe('UnauthMessagesService', () => { JSON.stringify([ { uuid, - missingDevices: [4, 5], - extraDevices: [40, 50], + devices: { + missingDevices: [4, 5], + extraDevices: [40, 50], + }, }, ]) ), @@ -165,7 +167,9 @@ describe('UnauthMessagesService', () => { JSON.stringify([ { uuid, - staleDevices: [4, 5], + devices: { + staleDevices: [4, 5], + }, }, ]) ), diff --git a/rust/net/chat/src/ws/messages.rs b/rust/net/chat/src/ws/messages.rs index 46788bf3e..c02a3b473 100644 --- a/rust/net/chat/src/ws/messages.rs +++ b/rust/net/chat/src/ws/messages.rs @@ -121,10 +121,16 @@ fn parse_multi_recipient_mismatched_devices_response( debug_assert_matches!(response.status.as_u16(), 409 | 410); #[derive(serde::Deserialize)] - #[serde(rename_all = "camelCase")] struct ParsedMismatchedDevicesEntry { #[serde(rename = "uuid")] service_id: String, + devices: ParsedMismatchedDevices, + } + + // Note: this can be shared with the 1:1 mismatched devices response. + #[derive(serde::Deserialize, Default, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + struct ParsedMismatchedDevices { // 409 fields #[serde(default)] missing_devices: Vec, @@ -158,10 +164,19 @@ fn parse_multi_recipient_mismatched_devices_response( .map(|entry| { let ParsedMismatchedDevicesEntry { service_id, + devices, + } = entry; + if devices == Default::default() { + return Err(CustomError::Unexpected { + log_safe: "no devices listed in mismatched device response".to_owned(), + }); + } + let ParsedMismatchedDevices { missing_devices, extra_devices, stale_devices, - } = entry; + } = devices; + Ok(MismatchedDeviceError { account: ServiceId::parse_from_service_id_string(&service_id).ok_or_else(|| { CustomError::Unexpected { @@ -224,8 +239,8 @@ mod test { #[test_case(empty(409) => matches Err(RequestError::Unexpected { .. }))] #[test_case(json( 409, format!(r#"[ - {{"uuid":"{ACI_UUID}","missingDevices":[50,60]}}, - {{"uuid":"PNI:{PNI_UUID}","missingDevices":[],"extraDevices":[4,5]}} + {{"uuid":"{ACI_UUID}","devices":{{"missingDevices":[50,60]}}}}, + {{"uuid":"PNI:{PNI_UUID}","devices":{{"missingDevices":[],"extraDevices":[4,5]}}}} ]"#) ) => matches Err(RequestError::Other(MrFailure::MismatchedDevices(errors))) if errors == [ MismatchedDeviceError { @@ -243,8 +258,8 @@ mod test { ])] #[test_case(json( 410, format!(r#"[ - {{"uuid":"{ACI_UUID}","staleDevices":[4,5]}}, - {{"uuid":"PNI:{PNI_UUID}","staleDevices":[]}} + {{"uuid":"{ACI_UUID}","devices":{{"staleDevices":[4,5]}}}}, + {{"uuid":"PNI:{PNI_UUID}","devices":{{"staleDevices":[1]}}}} ]"#) ) => matches Err(RequestError::Other(MrFailure::MismatchedDevices(errors))) if errors == [ MismatchedDeviceError { @@ -257,23 +272,26 @@ mod test { account: Pni::from(Uuid::try_parse(PNI_UUID).unwrap()).into(), missing_devices: vec![], extra_devices: vec![], - stale_devices: vec![], + stale_devices: vec![DeviceId::new(1).unwrap()], }, ])] #[test_case(json( 410, r#"["# ) => matches Err(RequestError::Unexpected { .. }))] #[test_case(json( - 410, format!(r#"{{"uuid":"{ACI_UUID}","staleDevices":[4,5]}}"#) + 410, format!(r#"{{"uuid":"{ACI_UUID}","devices":{{"staleDevices":[4,5]}}}}"#) ) => matches Err(RequestError::Unexpected { .. }))] #[test_case(json( - 410, r#"[{"uuid":"garbage","staleDevices":[4,5]}]"# + 410, format!(r#"[{{"uuid":"{ACI_UUID}","devices":{{}}}}]"#) ) => matches Err(RequestError::Unexpected { .. }))] #[test_case(json( - 410, format!(r#"{{"uuid":"{ACI_UUID}","staleDevices":[200]}}"#) + 410, r#"[{"uuid":"garbage","devices":{{"staleDevices":[4,5]}}}]"# ) => matches Err(RequestError::Unexpected { .. }))] #[test_case(json( - 410, format!(r#"{{"uuid":"{ACI_UUID}","staleDevices":["4"]}}"#) + 410, format!(r#"[{{"uuid":"{ACI_UUID}","devices":{{"staleDevices":[200]}}}}]"#) + ) => matches Err(RequestError::Unexpected { .. }))] + #[test_case(json( + 410, format!(r#"[{{"uuid":"{ACI_UUID}","devices":{{"staleDevices":["4"]}}}}]"#) ) => matches Err(RequestError::Unexpected { .. }))] fn test_story( response: Response, diff --git a/swift/Tests/LibSignalClientTests/ChatServices/UnauthMessagesServiceTests.swift b/swift/Tests/LibSignalClientTests/ChatServices/UnauthMessagesServiceTests.swift index 660b364d8..6dfb80cd7 100644 --- a/swift/Tests/LibSignalClientTests/ChatServices/UnauthMessagesServiceTests.swift +++ b/swift/Tests/LibSignalClientTests/ChatServices/UnauthMessagesServiceTests.swift @@ -86,8 +86,10 @@ class UnauthMessagesServiceTests: UnauthChatServiceTestBase