Fix sending messages to self when your session is deleted.

This commit is contained in:
Greyson Parrelli
2026-06-04 00:14:50 -04:00
parent f9b9ce6c14
commit 2c88945e6b
3 changed files with 38 additions and 16 deletions
@@ -2808,22 +2808,24 @@ public class SignalServiceMessageSender {
boolean story)
throws IOException, InvalidKeyException, UntrustedIdentityException
{
List<OutgoingPushMessage> messages = new LinkedList<>();
List<OutgoingPushMessage> messages = new LinkedList<>();
List<Integer> subDevices = aciStore.getSubDeviceSessions(recipient.getIdentifier());
Set<Integer> deviceIds = subDevices.stream()
.filter((id) -> aciStore.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), id)))
.collect(Collectors.toSet());
List<Integer> subDevices = aciStore.getSubDeviceSessions(recipient.getIdentifier());
List<Integer> deviceIds = new ArrayList<>(subDevices.size() + 1);
deviceIds.add(SignalServiceAddress.DEFAULT_DEVICE_ID);
deviceIds.addAll(subDevices);
if (recipient.matches(localAddress)) {
deviceIds.remove(Integer.valueOf(localDeviceId));
deviceIds.remove(localDeviceId);
if (deviceIds.isEmpty()) {
deviceIds.add(localDeviceId);
}
}
for (int deviceId : deviceIds) {
if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || aciStore.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) {
messages.add(getEncryptedMessage(recipient, sealedSenderAccess, deviceId, plaintext, story));
}
messages.add(getEncryptedMessage(recipient, sealedSenderAccess, deviceId, plaintext, story));
}
return new OutgoingPushMessageList(recipient.getIdentifier(), timestamp, messages, online, urgent);
@@ -396,17 +396,20 @@ open class MessageService(
}
private fun targetDeviceIds(serviceId: ServiceId): List<Int> {
val subDevices: MutableSet<Int> = (protocolStore.getSubDeviceSessions(serviceId.toString()) + SignalServiceAddress.DEFAULT_DEVICE_ID).toMutableSet()
val devices: MutableSet<Int> = protocolStore.getSubDeviceSessions(serviceId.toString())
.filter { protocolStore.containsSession(SignalProtocolAddress(serviceId.libSignalServiceId, it)) }
.toMutableSet()
devices += SignalServiceAddress.DEFAULT_DEVICE_ID
// When sending to self, skip our own device.
if (serviceId == localAddress.serviceId) {
subDevices -= localDeviceId
devices -= localDeviceId
if (devices.isEmpty()) {
devices += localDeviceId
}
}
return subDevices
.filter { it == SignalServiceAddress.DEFAULT_DEVICE_ID || protocolStore.containsSession(SignalProtocolAddress(serviceId.libSignalServiceId, it)) }
.sorted()
.toList()
return devices.sorted()
}
/**
@@ -228,6 +228,23 @@ class MessageServiceTest {
verify(exactly = 0) { cipher.encrypt(SignalProtocolAddress(recipientAci.libSignalServiceId, 3), any(), any()) }
}
@Test
fun `local send with no other devices sends to own device`() = runTest {
val service = newService()
val localProtocolAddress = SignalProtocolAddress(localAci.libSignalServiceId, SignalServiceAddress.DEFAULT_DEVICE_ID)
every { protocolStore.getSubDeviceSessions(localAci.toString()) } returns emptyList()
every { cipher.encrypt(any(), any(), any()) } returns OutgoingPushMessage(1, 1, 100, "AAAA")
coEvery { messageApi.sendSealedSenderMessage(any(), any(), any(), any(), any(), any()) } returns
RequestResult.Success(Unit)
val result = service.sendMessage(localAci, envelopeContent, timestamp, sealedSenderAccess = null, story = true, isOnline = false)
val success = (result as Either.Right).value
assertThat(success.devices).isEqualTo(listOf(SignalServiceAddress.DEFAULT_DEVICE_ID))
verify { cipher.encrypt(localProtocolAddress, null, envelopeContent) }
}
@Test
fun `MismatchedDeviceException archives extras and fetches missing prekeys`() = runTest {
val service = newService()