Fix call requests to a PNI.

This commit is contained in:
Cody Henthorne
2025-10-27 09:52:17 -04:00
committed by jeffrey-signal
parent 55617c18f0
commit 76e92f29b9
7 changed files with 129 additions and 21 deletions

View File

@@ -4,15 +4,22 @@ import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import java.nio.ByteBuffer;
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_CONNECTING;
@@ -31,6 +38,38 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
@NonNull RemotePeer remotePeer,
@NonNull OfferMessage.Type offerType)
{
if (!remotePeer.getRecipient().getHasAci()) {
Log.w(tag, "1:1 outgoing recipient is PNI only, send pseudo-call offer and terminate call");
remotePeer.setCallId(new CallId(ByteBuffer.wrap(Util.getSecretBytes(8)).getLong()));
currentState = currentState.builder()
.actionProcessor(new IdleActionProcessor(webRtcInteractor))
.changeCallInfoState()
.callRecipient(remotePeer.getRecipient())
.callState(WebRtcViewModel.State.CALL_NEEDS_PERMISSION)
.putParticipant(remotePeer.getRecipient(), CallParticipant.EMPTY)
.build();
boolean isVideoOffer = OfferMessage.Type.VIDEO_CALL == offerType;
SignalDatabase.calls().insertOneToOneCall(remotePeer.getCallId().longValue(),
System.currentTimeMillis(),
remotePeer.getId(),
isVideoOffer ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL,
CallTable.Direction.OUTGOING,
CallTable.Event.ONGOING);
webRtcInteractor.insertMissedCall(remotePeer, System.currentTimeMillis(), isVideoOffer, CallTable.Event.NOT_ACCEPTED);
webRtcInteractor.postStateUpdate(currentState);
webRtcInteractor.sendCallMessage(remotePeer, SignalServiceCallMessage.forOffer(new OfferMessage(remotePeer.getCallId().longValue(),
offerType,
new byte[0]),
null));
return terminate(currentState, remotePeer);
}
remotePeer.setCallStartTimestamp(System.currentTimeMillis());
currentState = currentState.builder()
@@ -60,7 +99,6 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
.build();
CallManager.CallMediaType callMediaType = WebRtcUtil.getCallMediaTypeFromOfferType(offerType);
try {
webRtcInteractor.getCallManager().call(remotePeer, callMediaType, SignalStore.account().getDeviceId());
} catch (CallException e) {

View File

@@ -49,12 +49,12 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
this.actionProcessorFactory = actionProcessorFactory;
}
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
@NonNull WebRtcData.OfferMetadata offerMetadata,
@NonNull WebRtcData.ReceivedOfferMetadata receivedOfferMetadata)
protected @NonNull WebRtcServiceState handleValidatedReceivedOffer(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
@NonNull WebRtcData.OfferMetadata offerMetadata,
@NonNull WebRtcData.ReceivedOfferMetadata receivedOfferMetadata)
{
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
Log.i(tag, "handleValidatedReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
Log.i(tag, "In a group call, send busy back to 1:1 call offer.");
currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true);

View File

@@ -55,6 +55,7 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import java.util.Collection;
@@ -167,16 +168,20 @@ public abstract class WebRtcActionProcessor {
//region Incoming call
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
@NonNull CallMetadata callMetadata,
@NonNull OfferMetadata offerMetadata,
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
protected final @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
@NonNull CallMetadata callMetadata,
@NonNull OfferMetadata offerMetadata,
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
{
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + " offer_type:" + offerMetadata.getOfferType());
if (TelephonyUtil.isAnyPstnLineBusy(context)) {
Log.i(tag, "PSTN line is busy.");
currentState = currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true);
if (receivedOfferMetadata.getDestinationServiceId() instanceof ServiceId.PNI) {
if (RecipientUtil.isCallRequestAccepted(callMetadata.getRemotePeer().getRecipient())) {
Log.i(tag, "Caller is trusted but called our PNI, insert missed call and send hangup as we can't proceed.");
currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NORMAL), true);
} else {
Log.i(tag, "Caller is untrusted but called our PNI, insert missed call and do not send hangup.");
}
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
@@ -188,6 +193,13 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
if (TelephonyUtil.isAnyPstnLineBusy(context)) {
Log.i(tag, "PSTN line is busy.");
currentState = currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
if (offerMetadata.getOpaque() == null) {
Log.w(tag, "Opaque data is required.");
currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NORMAL), true);
@@ -195,6 +207,13 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
if (receivedOfferMetadata.getRemoteIdentityKey() == null) {
Log.w(tag, "Unable to locate remote identity key for caller, bailing");
currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NORMAL), true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
NotificationProfile activeProfile = NotificationProfiles.getActiveProfile(SignalDatabase.notificationProfiles().getProfiles());
if (activeProfile != null && !(activeProfile.isRecipientAllowed(callMetadata.getRemotePeer().getId()) || activeProfile.getAllowAllCalls())) {
Log.w(tag, "Caller is excluded by notification profile.");
@@ -202,7 +221,15 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
Log.i(tag, "add remotePeer callId: " + callMetadata.getRemotePeer().getCallId() + " key: " + callMetadata.getRemotePeer().hashCode());
return handleValidatedReceivedOffer(currentState, callMetadata, offerMetadata, receivedOfferMetadata);
}
protected @NonNull WebRtcServiceState handleValidatedReceivedOffer(@NonNull WebRtcServiceState currentState,
@NonNull CallMetadata callMetadata,
@NonNull OfferMetadata offerMetadata,
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
{
Log.i(tag, "handleValidatedReceivedOffer(): add remotePeer callId: " + callMetadata.getRemotePeer().getCallId() + " key: " + callMetadata.getRemotePeer().hashCode());
callMetadata.getRemotePeer().setCallStartTimestamp(receivedOfferMetadata.getServerReceivedTimestamp());

View File

@@ -8,6 +8,7 @@ import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.UUID;
@@ -66,17 +67,27 @@ public class WebRtcData {
* Additional metadata for a received call.
*/
public static class ReceivedOfferMetadata {
private final @NonNull byte[] remoteIdentityKey;
private final long serverReceivedTimestamp;
private final long serverDeliveredTimestamp;
private final @NonNull ServiceId destinationServiceId;
private final @Nullable byte[] remoteIdentityKey;
private final long serverReceivedTimestamp;
private final long serverDeliveredTimestamp;
public ReceivedOfferMetadata(@NonNull byte[] remoteIdentityKey, long serverReceivedTimestamp, long serverDeliveredTimestamp) {
public ReceivedOfferMetadata(@NonNull ServiceId destinationServiceId,
@Nullable byte[] remoteIdentityKey,
long serverReceivedTimestamp,
long serverDeliveredTimestamp)
{
this.destinationServiceId = destinationServiceId;
this.remoteIdentityKey = remoteIdentityKey;
this.serverReceivedTimestamp = serverReceivedTimestamp;
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
}
@NonNull byte[] getRemoteIdentityKey() {
@NonNull ServiceId getDestinationServiceId() {
return destinationServiceId;
}
@Nullable byte[] getRemoteIdentityKey() {
return remoteIdentityKey;
}