Use account device IDs for calls and verify primary and linked configurations (#2128)

* Fix voice calls from linked devices

(cherry picked from commit f3e22de14e4463adb785fb797791bc826b765a10)

* Verify primary and linked call configuration through account state

---------

Co-authored-by: Daniel <dan@gainengineering.com.au>
This commit is contained in:
Robert Nisipeanu 2026-10-06 21:10:53 +03:00 committed by GitHub
parent ca2583c1b0
commit ebf24ee0b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 1 deletions

View File

@ -458,10 +458,14 @@ public class CallManager implements AutoCloseable {
}
private String buildConfig(CallState state) {
return buildConfig(state, account.getDeviceId());
}
static String buildConfig(CallState state, int localDeviceId) {
var config = mapper.createObjectNode();
config.put("call_id", Utils.callIdUnsigned(state.callId));
config.put("is_outgoing", state.isOutgoing);
config.put("local_device_id", 1);
config.put("local_device_id", localDeviceId);
return writeJson(config);
}

View File

@ -1,16 +1,22 @@
package org.asamk.signal.manager.helper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.asamk.signal.manager.api.CallInfo;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.TestRecipientId;
import org.asamk.signal.manager.util.Utils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.math.BigInteger;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@ -23,6 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
class CallManagerTest {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// --- Reflection helpers for private static methods ---
private static final MethodHandle GET_RAW_IDENTITY_KEY_BYTES;
@ -177,6 +185,38 @@ class CallManagerTest {
assertTrue(foundDifferent, "generateCallId returned same value 21 times in a row");
}
@Test
void buildConfig_usesProvidedLinkedDeviceId() throws Exception {
var state = makeCallState(123L, CallInfo.State.RINGING_OUTGOING);
var config = OBJECT_MAPPER.readTree(CallManager.buildConfig(state, 7));
assertEquals(7, config.get("local_device_id").intValue());
assertEquals(123L, config.get("call_id").longValue());
assertTrue(config.get("is_outgoing").booleanValue());
}
@ParameterizedTest
@CsvSource({"1, true", "1, false", "2, true", "2, false", "127, true", "127, false"})
void buildConfig_readsAccountDeviceId(int deviceId, boolean outgoing) throws Exception {
var constructor = SignalAccount.class.getDeclaredConstructor(FileChannel.class, FileLock.class);
constructor.setAccessible(true);
var account = constructor.newInstance(null, null);
var deviceField = SignalAccount.class.getDeclaredField("deviceId");
deviceField.setAccessible(true);
deviceField.setInt(account, deviceId);
var builder = CallManager.class.getDeclaredMethod("buildConfig", CallManager.CallState.class);
builder.setAccessible(true);
try (var manager = new CallManager(new Context(account, null, null, null, null, null))) {
var state = new CallManager.CallState(-1L, CallInfo.State.IDLE, null, null, outgoing);
var config = new ObjectMapper().readTree((String) builder.invoke(manager, state));
assertEquals(deviceId, config.path("local_device_id").asInt());
assertEquals(outgoing, config.path("is_outgoing").asBoolean());
assertEquals("18446744073709551615", config.path("call_id").asText());
}
}
// ========================================================================
// handleStateChange state machine tests
//