fixed race conditions around PROCEED state. fixes #3989

This commit is contained in:
Daniel Gultsch 2021-02-11 16:56:57 +01:00
parent 6e3dc0eef6
commit 7330d8a7f0
2 changed files with 68 additions and 17 deletions

View File

@ -126,6 +126,7 @@ public abstract class AbstractJingleConnection {
ACCEPTED, ACCEPTED,
PROCEED, PROCEED,
REJECTED, REJECTED,
REJECTED_RACED, //used when we want to reject but havent received session init yet
RETRACTED, RETRACTED,
RETRACTED_RACED, //used when receiving a retract after we already asked to proceed RETRACTED_RACED, //used when receiving a retract after we already asked to proceed
SESSION_INITIALIZED, //equal to 'PENDING' SESSION_INITIALIZED, //equal to 'PENDING'

View File

@ -60,6 +60,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private static final List<State> TERMINATED = Arrays.asList( private static final List<State> TERMINATED = Arrays.asList(
State.ACCEPTED, State.ACCEPTED,
State.REJECTED, State.REJECTED,
State.REJECTED_RACED,
State.RETRACTED, State.RETRACTED,
State.RETRACTED_RACED, State.RETRACTED_RACED,
State.TERMINATED_SUCCESS, State.TERMINATED_SUCCESS,
@ -87,6 +88,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
State.TERMINATED_CONNECTIVITY_ERROR //only used when the xmpp connection rebinds State.TERMINATED_CONNECTIVITY_ERROR //only used when the xmpp connection rebinds
)); ));
transitionBuilder.put(State.PROCEED, ImmutableList.of( transitionBuilder.put(State.PROCEED, ImmutableList.of(
State.REJECTED_RACED,
State.RETRACTED_RACED, State.RETRACTED_RACED,
State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_INITIALIZED_PRE_APPROVED,
State.TERMINATED_SUCCESS, State.TERMINATED_SUCCESS,
@ -523,31 +525,55 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
} }
private void receiveReject(Jid from, String serverMsgId, long timestamp) { private void receiveReject(final Jid from, final String serverMsgId, final long timestamp) {
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid()); final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
//reject from another one of my clients //reject from another one of my clients
if (originatedFromMyself) { if (originatedFromMyself) {
if (transition(State.REJECTED)) { receiveRejectFromMyself(serverMsgId, timestamp);
this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); } else if (isInitiator()) {
this.finish(); if (from.equals(id.with)) {
if (serverMsgId != null) { receiveRejectFromResponder();
this.message.setServerMsgId(serverMsgId);
}
this.message.setTime(timestamp);
this.message.setCarbon(true); //indicate that call was rejected on other device
writeLogMessageMissed();
} else { } else {
Log.d(Config.LOGTAG, "not able to transition into REJECTED because already in " + this.state); Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
} }
} else { } else {
Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with); Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
} }
} }
private void receiveRejectFromMyself(String serverMsgId, long timestamp) {
if (transition(State.REJECTED)) {
this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
this.finish();
if (serverMsgId != null) {
this.message.setServerMsgId(serverMsgId);
}
this.message.setTime(timestamp);
this.message.setCarbon(true); //indicate that call was rejected on other device
writeLogMessageMissed();
} else {
Log.d(Config.LOGTAG, "not able to transition into REJECTED because already in " + this.state);
}
}
private void receiveRejectFromResponder() {
if (isInState(State.PROCEED)) {
Log.d(Config.LOGTAG, id.account.getJid() + ": received reject while still in proceed. callee reconsidered");
closeTransitionLogFinish(State.REJECTED_RACED);
return;
}
if (isInState(State.SESSION_INITIALIZED_PRE_APPROVED)) {
Log.d(Config.LOGTAG, id.account.getJid() + ": received reject while in SESSION_INITIATED_PRE_APPROVED. callee reconsidered before receiving session-init");
closeTransitionLogFinish(State.TERMINATED_DECLINED_OR_BUSY);
return;
}
Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from responder because already in state " + this.state);
}
private void receivePropose(final Jid from, final Propose propose, final String serverMsgId, final long timestamp) { private void receivePropose(final Jid from, final Propose propose, final String serverMsgId, final long timestamp) {
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid()); final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
if (originatedFromMyself) { if (originatedFromMyself) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from myself. ignoring");
} else if (transition(State.PROPOSED, () -> { } else if (transition(State.PROPOSED, () -> {
final Collection<RtpDescription> descriptions = Collections2.transform( final Collection<RtpDescription> descriptions = Collections2.transform(
Collections2.filter(propose.getDescriptions(), d -> d instanceof RtpDescription), Collections2.filter(propose.getDescriptions(), d -> d instanceof RtpDescription),
@ -830,6 +856,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR;
} }
case REJECTED: case REJECTED:
case REJECTED_RACED:
case TERMINATED_DECLINED_OR_BUSY: case TERMINATED_DECLINED_OR_BUSY:
if (isInitiator()) { if (isInitiator()) {
return RtpEndUserState.DECLINED_OR_BUSY; return RtpEndUserState.DECLINED_OR_BUSY;
@ -842,7 +869,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
case TERMINATED_CANCEL_OR_TIMEOUT: case TERMINATED_CANCEL_OR_TIMEOUT:
return RtpEndUserState.ENDED; return RtpEndUserState.ENDED;
case RETRACTED_RACED: case RETRACTED_RACED:
return RtpEndUserState.RETRACTED; if (isInitiator()) {
return RtpEndUserState.ENDED;
} else {
return RtpEndUserState.RETRACTED;
}
case TERMINATED_CONNECTIVITY_ERROR: case TERMINATED_CONNECTIVITY_ERROR:
return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR;
case TERMINATED_APPLICATION_FAILURE: case TERMINATED_APPLICATION_FAILURE:
@ -938,10 +969,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return; return;
} }
if (isInState(State.PROCEED)) { if (isInState(State.PROCEED)) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ending call while in state PROCEED just means ending the connection"); if (isInitiator()) {
this.webRTCWrapper.close(); retractFromProceed();
transitionOrThrow(State.TERMINATED_SUCCESS); //arguably this wasn't success; but not a real failure either } else {
this.finish(); rejectCallFromProceed();
}
return; return;
} }
if (isInitiator() && isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED)) { if (isInitiator() && isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED)) {
@ -965,6 +997,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
throw new IllegalStateException("called 'endCall' while in state " + this.state + ". isInitiator=" + isInitiator()); throw new IllegalStateException("called 'endCall' while in state " + this.state + ". isInitiator=" + isInitiator());
} }
private void retractFromProceed() {
Log.d(Config.LOGTAG, "retract from proceed");
this.sendJingleMessage("retract");
closeTransitionLogFinish(State.RETRACTED_RACED);
}
private void closeTransitionLogFinish(final State state) {
this.webRTCWrapper.close();
transitionOrThrow(state);
writeLogMessage(state);
finish();
}
private void setupWebRTC(final Set<Media> media, final List<PeerConnection.IceServer> iceServers) throws WebRTCWrapper.InitializationException { private void setupWebRTC(final Set<Media> media, final List<PeerConnection.IceServer> iceServers) throws WebRTCWrapper.InitializationException {
this.jingleConnectionManager.ensureConnectionIsRegistered(this); this.jingleConnectionManager.ensureConnectionIsRegistered(this);
final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference; final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference;
@ -992,6 +1037,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
finish(); finish();
} }
private void rejectCallFromProceed() {
this.sendJingleMessage("reject");
closeTransitionLogFinish(State.REJECTED_RACED);
}
private void rejectCallFromSessionInitiate() { private void rejectCallFromSessionInitiate() {
webRTCWrapper.close(); webRTCWrapper.close();
sendSessionTerminate(Reason.DECLINE); sendSessionTerminate(Reason.DECLINE);