JingleRtpConnection code clean up

This commit is contained in:
Daniel Gultsch 2023-11-13 12:54:55 +01:00
parent 1471969237
commit 80c49955f0
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2

View file

@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.FutureCallback;
@ -26,23 +25,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.PeerConnection;
import org.webrtc.VideoTrack;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@ -61,7 +43,6 @@ import eu.siacs.conversations.utils.IP;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
@ -73,6 +54,23 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.PeerConnection;
import org.webrtc.VideoTrack;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class JingleRtpConnection extends AbstractJingleConnection
implements WebRTCWrapper.EventCallback {
@ -195,64 +193,37 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private static State reasonToState(Reason reason) {
switch (reason) {
case SUCCESS:
return State.TERMINATED_SUCCESS;
case DECLINE:
case BUSY:
return State.TERMINATED_DECLINED_OR_BUSY;
case CANCEL:
case TIMEOUT:
return State.TERMINATED_CANCEL_OR_TIMEOUT;
case SECURITY_ERROR:
return State.TERMINATED_SECURITY_ERROR;
case FAILED_APPLICATION:
case UNSUPPORTED_TRANSPORTS:
case UNSUPPORTED_APPLICATIONS:
return State.TERMINATED_APPLICATION_FAILURE;
default:
return State.TERMINATED_CONNECTIVITY_ERROR;
}
return switch (reason) {
case SUCCESS -> State.TERMINATED_SUCCESS;
case DECLINE, BUSY -> State.TERMINATED_DECLINED_OR_BUSY;
case CANCEL, TIMEOUT -> State.TERMINATED_CANCEL_OR_TIMEOUT;
case SECURITY_ERROR -> State.TERMINATED_SECURITY_ERROR;
case FAILED_APPLICATION, UNSUPPORTED_TRANSPORTS, UNSUPPORTED_APPLICATIONS -> State
.TERMINATED_APPLICATION_FAILURE;
default -> State.TERMINATED_CONNECTIVITY_ERROR;
};
}
@Override
synchronized void deliverPacket(final JinglePacket jinglePacket) {
switch (jinglePacket.getAction()) {
case SESSION_INITIATE:
receiveSessionInitiate(jinglePacket);
break;
case TRANSPORT_INFO:
receiveTransportInfo(jinglePacket);
break;
case SESSION_ACCEPT:
receiveSessionAccept(jinglePacket);
break;
case SESSION_TERMINATE:
receiveSessionTerminate(jinglePacket);
break;
case CONTENT_ADD:
receiveContentAdd(jinglePacket);
break;
case CONTENT_ACCEPT:
receiveContentAccept(jinglePacket);
break;
case CONTENT_REJECT:
receiveContentReject(jinglePacket);
break;
case CONTENT_REMOVE:
receiveContentRemove(jinglePacket);
break;
case CONTENT_MODIFY:
receiveContentModify(jinglePacket);
break;
default:
case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket);
case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket);
case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket);
case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket);
case CONTENT_ADD -> receiveContentAdd(jinglePacket);
case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket);
case CONTENT_REJECT -> receiveContentReject(jinglePacket);
case CONTENT_REMOVE -> receiveContentRemove(jinglePacket);
case CONTENT_MODIFY -> receiveContentModify(jinglePacket);
default -> {
respondOk(jinglePacket);
Log.d(
Config.LOGTAG,
String.format(
"%s: received unhandled jingle action %s",
id.account.getJid().asBareJid(), jinglePacket.getAction()));
break;
}
}
}
@ -354,15 +325,22 @@ public class JingleRtpConnection extends AbstractJingleConnection
final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates =
contentMap.contents.entrySet();
final RtpContentMap remote = getRemoteContentMap();
final Set<String> remoteContentIds = remote == null ? Collections.emptySet() : remote.contents.keySet();
final Set<String> remoteContentIds =
remote == null ? Collections.emptySet() : remote.contents.keySet();
if (Collections.disjoint(remoteContentIds, contentMap.contents.keySet())) {
Log.d(Config.LOGTAG,"received transport-info for unknown contents "+contentMap.contents.keySet()+" (known: "+remoteContentIds+")");
Log.d(
Config.LOGTAG,
"received transport-info for unknown contents "
+ contentMap.contents.keySet()
+ " (known: "
+ remoteContentIds
+ ")");
respondOk(jinglePacket);
pendingIceCandidates.addAll(candidates);
return;
}
if (this.state != State.SESSION_ACCEPTED) {
Log.d(Config.LOGTAG,"received transport-info prematurely. adding to backlog");
Log.d(Config.LOGTAG, "received transport-info prematurely. adding to backlog");
respondOk(jinglePacket);
pendingIceCandidates.addAll(candidates);
return;
@ -401,26 +379,31 @@ public class JingleRtpConnection extends AbstractJingleConnection
final boolean hasFullTransportInfo = modification.hasFullTransportInfo();
final ListenableFuture<RtpContentMap> future =
receiveRtpContentMap(
modification, this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
receiveContentAdd(jinglePacket, rtpContentMap);
}
modification,
this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(
future,
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
receiveContentAdd(jinglePacket, rtpContentMap);
}
@Override
public void onFailure(@NonNull Throwable throwable) {
respondOk(jinglePacket);
final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": improperly formatted contents in content-add",
throwable);
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
}
}, MoreExecutors.directExecutor());
@Override
public void onFailure(@NonNull Throwable throwable) {
respondOk(jinglePacket);
final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": improperly formatted contents in content-add",
throwable);
webRTCWrapper.close();
sendSessionTerminate(
Reason.ofThrowable(rootCause), rootCause.getMessage());
}
},
MoreExecutors.directExecutor());
} else {
terminateWithOutOfOrder(jinglePacket);
}
@ -508,19 +491,24 @@ public class JingleRtpConnection extends AbstractJingleConnection
final boolean hasFullTransportInfo = receivedContentAccept.hasFullTransportInfo();
final ListenableFuture<RtpContentMap> future =
receiveRtpContentMap(
receivedContentAccept, this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap result) {
receiveContentAccept(result);
}
receivedContentAccept,
this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(
future,
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap result) {
receiveContentAccept(result);
}
@Override
public void onFailure(@NonNull final Throwable throwable) {
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
}
}, MoreExecutors.directExecutor());
@Override
public void onFailure(@NonNull final Throwable throwable) {
webRTCWrapper.close();
sendSessionTerminate(
Reason.ofThrowable(throwable), throwable.getMessage());
}
},
MoreExecutors.directExecutor());
} else {
Log.d(Config.LOGTAG, "received content-accept did not match our outgoing content-add");
terminateWithOutOfOrder(jinglePacket);
@ -573,25 +561,34 @@ public class JingleRtpConnection extends AbstractJingleConnection
final boolean isInitiator = isInitiator();
final RtpContentMap currentOutgoing = this.outgoingContentAdd;
final RtpContentMap remoteContentMap = this.getRemoteContentMap();
final Set<String> currentOutgoingMediaIds = currentOutgoing == null ? Collections.emptySet() : currentOutgoing.contents.keySet();
final Set<String> currentOutgoingMediaIds =
currentOutgoing == null
? Collections.emptySet()
: currentOutgoing.contents.keySet();
Log.d(Config.LOGTAG, "receiveContentModification(" + modification + ")");
if (currentOutgoing != null && currentOutgoingMediaIds.containsAll(modification.keySet())) {
respondOk(jinglePacket);
final RtpContentMap modifiedContentMap;
try {
modifiedContentMap = currentOutgoing.modifiedSendersChecked(isInitiator, modification);
modifiedContentMap =
currentOutgoing.modifiedSendersChecked(isInitiator, modification);
} catch (final IllegalArgumentException e) {
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
}
this.outgoingContentAdd = modifiedContentMap;
Log.d(Config.LOGTAG, id.account.getJid().asBareJid()+": processed content-modification for pending content-add");
} else if (remoteContentMap != null && remoteContentMap.contents.keySet().containsAll(modification.keySet())) {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": processed content-modification for pending content-add");
} else if (remoteContentMap != null
&& remoteContentMap.contents.keySet().containsAll(modification.keySet())) {
respondOk(jinglePacket);
final RtpContentMap modifiedRemoteContentMap;
try {
modifiedRemoteContentMap = remoteContentMap.modifiedSendersChecked(isInitiator, modification);
modifiedRemoteContentMap =
remoteContentMap.modifiedSendersChecked(isInitiator, modification);
} catch (final IllegalArgumentException e) {
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
@ -601,20 +598,27 @@ public class JingleRtpConnection extends AbstractJingleConnection
try {
offer = SessionDescription.of(modifiedRemoteContentMap, !isInitiator());
} catch (final IllegalArgumentException | NullPointerException e) {
Log.d(Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": unable convert offer from content-modify to SDP", e);
Log.d(
Config.LOGTAG,
id.getAccount().getJid().asBareJid()
+ ": unable convert offer from content-modify to SDP",
e);
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
}
Log.d(Config.LOGTAG, id.account.getJid().asBareJid()+": auto accepting content-modification");
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid() + ": auto accepting content-modification");
this.autoAcceptContentModify(modifiedRemoteContentMap, offer);
} else {
Log.d(Config.LOGTAG,"received unsupported content modification "+modification);
Log.d(Config.LOGTAG, "received unsupported content modification " + modification);
respondWithItemNotFound(jinglePacket);
}
}
private void autoAcceptContentModify(final RtpContentMap modifiedRemoteContentMap, final SessionDescription offer) {
private void autoAcceptContentModify(
final RtpContentMap modifiedRemoteContentMap, final SessionDescription offer) {
this.setRemoteContentMap(modifiedRemoteContentMap);
final org.webrtc.SessionDescription sdp =
new org.webrtc.SessionDescription(
@ -625,8 +629,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
final SessionDescription answer = setLocalSessionDescription();
final RtpContentMap rtpContentMap = RtpContentMap.of(answer, isInitiator());
modifyLocalContentMap(rtpContentMap);
// we do not need to send an answer but do we have to resend the candidates currently in SDP?
//resendCandidatesFromSdp(answer);
// we do not need to send an answer but do we have to resend the candidates currently in
// SDP?
// resendCandidatesFromSdp(answer);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
} catch (final Exception e) {
Log.d(Config.LOGTAG, "unable to accept content add", Throwables.getRootCause(e));
@ -635,17 +640,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
private static ImmutableMultimap<String, IceUdpTransportInfo.Candidate> parseCandidates(final SessionDescription answer) {
final ImmutableMultimap.Builder<String, IceUdpTransportInfo.Candidate> candidateBuilder = new ImmutableMultimap.Builder<>();
for(final SessionDescription.Media media : answer.media) {
private static ImmutableMultimap<String, IceUdpTransportInfo.Candidate> parseCandidates(
final SessionDescription answer) {
final ImmutableMultimap.Builder<String, IceUdpTransportInfo.Candidate> candidateBuilder =
new ImmutableMultimap.Builder<>();
for (final SessionDescription.Media media : answer.media) {
final String mid = Iterables.getFirst(media.attributes.get("mid"), null);
if (Strings.isNullOrEmpty(mid)) {
continue;
}
for(final String sdpCandidate : media.attributes.get("candidate")) {
final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttributeValue(sdpCandidate, null);
for (final String sdpCandidate : media.attributes.get("candidate")) {
final IceUdpTransportInfo.Candidate candidate =
IceUdpTransportInfo.Candidate.fromSdpAttributeValue(sdpCandidate, null);
if (candidate != null) {
candidateBuilder.put(mid,candidate);
candidateBuilder.put(mid, candidate);
}
}
}
@ -677,7 +685,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
if (ourSummary.equals(ContentAddition.summary(receivedContentReject))) {
this.outgoingContentAdd = null;
respondOk(jinglePacket);
Log.d(Config.LOGTAG,jinglePacket.toString());
Log.d(Config.LOGTAG, jinglePacket.toString());
receiveContentReject(ourSummary);
} else {
Log.d(Config.LOGTAG, "received content-reject did not match our outgoing content-add");
@ -813,7 +821,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
"Unexpected rollback condition. Senders were not uniformly none");
}
public synchronized void acceptContentAdd(@NonNull final Set<ContentAddition.Summary> contentAddition) {
public synchronized void acceptContentAdd(
@NonNull final Set<ContentAddition.Summary> contentAddition) {
final RtpContentMap incomingContentAdd = this.incomingContentAdd;
if (incomingContentAdd == null) {
throw new IllegalStateException("No incoming content add");
@ -822,37 +831,61 @@ public class JingleRtpConnection extends AbstractJingleConnection
if (contentAddition.equals(ContentAddition.summary(incomingContentAdd))) {
this.incomingContentAdd = null;
final Set<Content.Senders> senders = incomingContentAdd.getSenders();
Log.d(Config.LOGTAG,"senders of incoming content-add: "+senders);
Log.d(Config.LOGTAG, "senders of incoming content-add: " + senders);
if (senders.equals(Content.Senders.receiveOnly(isInitiator()))) {
Log.d(Config.LOGTAG,"content addition is receive only. we want to upgrade to 'both'");
final RtpContentMap modifiedSenders = incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
final JinglePacket proposedContentModification = modifiedSenders.toStub().toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId);
Log.d(
Config.LOGTAG,
"content addition is receive only. we want to upgrade to 'both'");
final RtpContentMap modifiedSenders =
incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
final JinglePacket proposedContentModification =
modifiedSenders
.toStub()
.toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId);
proposedContentModification.setTo(id.with);
xmppConnectionService.sendIqPacket(id.account, proposedContentModification, (account, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": remote has accepted our upgrade to senders=both");
acceptContentAdd(ContentAddition.summary(modifiedSenders), modifiedSenders);
} else {
Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": remote has rejected our upgrade to senders=both");
acceptContentAdd(contentAddition, incomingContentAdd);
}
});
xmppConnectionService.sendIqPacket(
id.account,
proposedContentModification,
(account, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": remote has accepted our upgrade to senders=both");
acceptContentAdd(
ContentAddition.summary(modifiedSenders), modifiedSenders);
} else {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": remote has rejected our upgrade to senders=both");
acceptContentAdd(contentAddition, incomingContentAdd);
}
});
}
} else {
throw new IllegalStateException("Accepted content add does not match pending content-add");
throw new IllegalStateException(
"Accepted content add does not match pending content-add");
}
}
private void acceptContentAdd(@NonNull final Set<ContentAddition.Summary> contentAddition, final RtpContentMap incomingContentAdd) {
private void acceptContentAdd(
@NonNull final Set<ContentAddition.Summary> contentAddition,
final RtpContentMap incomingContentAdd) {
final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup();
final RtpContentMap modifiedContentMap = getRemoteContentMap().addContent(incomingContentAdd, setup);
final RtpContentMap modifiedContentMap =
getRemoteContentMap().addContent(incomingContentAdd, setup);
this.setRemoteContentMap(modifiedContentMap);
final SessionDescription offer;
try {
offer = SessionDescription.of(modifiedContentMap, !isInitiator());
} catch (final IllegalArgumentException | NullPointerException e) {
Log.d(Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": unable convert offer from content-add to SDP", e);
Log.d(
Config.LOGTAG,
id.getAccount().getJid().asBareJid()
+ ": unable convert offer from content-add to SDP",
e);
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
@ -893,10 +926,11 @@ public class JingleRtpConnection extends AbstractJingleConnection
addIceCandidatesFromBlackLog();
modifyLocalContentMap(rtpContentMap);
final ListenableFuture<RtpContentMap> future = prepareOutgoingContentMap(contentAcceptMap);
final ListenableFuture<RtpContentMap> future =
prepareOutgoingContentMap(contentAcceptMap);
Futures.addCallback(
future,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
sendContentAccept(rtpContentMap);
@ -917,7 +951,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void sendContentAccept(final RtpContentMap contentAcceptMap) {
final JinglePacket jinglePacket = contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId);
final JinglePacket jinglePacket =
contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId);
send(jinglePacket);
}
@ -963,7 +998,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
// ICE-restart
// and if that's the case we are seeing an answer.
// This might be more spec compliant but also more error prone potentially
final boolean isSignalStateStable = this.webRTCWrapper.getSignalingState() == PeerConnection.SignalingState.STABLE;
final boolean isSignalStateStable =
this.webRTCWrapper.getSignalingState() == PeerConnection.SignalingState.STABLE;
// TODO a stable signal state can be another indicator that we have an offer to restart ICE
final boolean isOffer = rtpContentMap.emptyCandidates();
final RtpContentMap restartContentMap;
@ -1027,7 +1063,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
final RtpContentMap restartContentMap,
final boolean isOffer)
throws ExecutionException, InterruptedException {
final SessionDescription sessionDescription = SessionDescription.of(restartContentMap, !isInitiator());
final SessionDescription sessionDescription =
SessionDescription.of(restartContentMap, !isInitiator());
final org.webrtc.SessionDescription.Type type =
isOffer
? org.webrtc.SessionDescription.Type.OFFER
@ -1126,8 +1163,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
} catch (final Exception e) {
return Futures.immediateFailedFuture(e);
}
}
private ListenableFuture<RtpContentMap> receiveRtpContentMap(final RtpContentMap receivedContentMap, final boolean expectVerification) {
}
private ListenableFuture<RtpContentMap> receiveRtpContentMap(
final RtpContentMap receivedContentMap, final boolean expectVerification) {
Log.d(
Config.LOGTAG,
"receiveRtpContentMap("
@ -1183,7 +1222,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
Futures.addCallback(
future,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
receiveSessionInitiate(jinglePacket, rtpContentMap);
@ -1272,7 +1311,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
Futures.addCallback(
future,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
receiveSessionAccept(jinglePacket, rtpContentMap);
@ -1438,7 +1477,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
}
private void failureToPerformAction(final JinglePacket.Action action, final Throwable throwable) {
private void failureToPerformAction(
final JinglePacket.Action action, final Throwable throwable) {
if (isTerminated()) {
return;
}
@ -1459,7 +1499,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void prepareSessionAccept(
final org.webrtc.SessionDescription webRTCSessionDescription, final boolean includeCandidates) {
final org.webrtc.SessionDescription webRTCSessionDescription,
final boolean includeCandidates) {
final SessionDescription sessionDescription =
SessionDescription.parse(webRTCSessionDescription.description);
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription, false);
@ -1475,7 +1516,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
prepareOutgoingContentMap(respondingRtpContentMap);
Futures.addCallback(
outgoingContentMapFuture,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap outgoingContentMap) {
if (includeCandidates) {
@ -1548,30 +1589,23 @@ public class JingleRtpConnection extends AbstractJingleConnection
+ ": delivered message to JingleRtpConnection "
+ message);
switch (message.getName()) {
case "propose":
receivePropose(from, Propose.upgrade(message), serverMessageId, timestamp);
break;
case "proceed":
receiveProceed(from, Proceed.upgrade(message), serverMessageId, timestamp);
break;
case "retract":
receiveRetract(from, serverMessageId, timestamp);
break;
case "reject":
receiveReject(from, serverMessageId, timestamp);
break;
case "accept":
receiveAccept(from, serverMessageId, timestamp);
break;
default:
break;
case "propose" -> receivePropose(
from, Propose.upgrade(message), serverMessageId, timestamp);
case "proceed" -> receiveProceed(
from, Proceed.upgrade(message), serverMessageId, timestamp);
case "retract" -> receiveRetract(from, serverMessageId, timestamp);
case "reject" -> receiveReject(from, serverMessageId, timestamp);
case "accept" -> receiveAccept(from, serverMessageId, timestamp);
}
}
void deliverFailedProceed(final String message) {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid() + ": receive message error for proceed message ("+Strings.nullToEmpty(message)+")");
id.account.getJid().asBareJid()
+ ": receive message error for proceed message ("
+ Strings.nullToEmpty(message)
+ ")");
if (transition(State.TERMINATED_CONNECTIVITY_ERROR)) {
webRTCWrapper.close();
Log.d(
@ -1609,9 +1643,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
this.message.setTime(timestamp);
this.message.setCarbon(true); // indicate that call was accepted on other device
this.writeLogMessageSuccess(0);
this.xmppConnectionService
.getNotificationService()
.cancelIncomingCallNotification();
this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
this.finish();
}
@ -1749,14 +1781,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
private synchronized void ringingTimeout() {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": timeout reached for ringing");
switch (this.state) {
case PROPOSED:
case PROPOSED -> {
message.markUnread();
rejectCallFromProposed();
break;
case SESSION_INITIALIZED:
}
case SESSION_INITIALIZED -> {
message.markUnread();
rejectCallFromSessionInitiate();
break;
}
}
xmppConnectionService.getNotificationService().pushMissedCallNow(message);
}
@ -1932,7 +1964,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void prepareSessionInitiate(
final org.webrtc.SessionDescription webRTCSessionDescription, final boolean includeCandidates, final State targetState) {
final org.webrtc.SessionDescription webRTCSessionDescription,
final boolean includeCandidates,
final State targetState) {
final SessionDescription sessionDescription =
SessionDescription.parse(webRTCSessionDescription.description);
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription, true);
@ -1947,7 +1981,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
encryptSessionInitiate(rtpContentMap);
Futures.addCallback(
outgoingContentMapFuture,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap outgoingContentMap) {
if (includeCandidates) {
@ -1956,7 +1990,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
"including "
+ candidates.size()
+ " candidates in session initiate");
sendSessionInitiate(outgoingContentMap.withCandidates(candidates), targetState);
sendSessionInitiate(
outgoingContentMap.withCandidates(candidates), targetState);
webRTCWrapper.resetPendingCandidates();
} else {
sendSessionInitiate(outgoingContentMap, targetState);
@ -2170,59 +2205,62 @@ public class JingleRtpConnection extends AbstractJingleConnection
public RtpEndUserState getEndUserState() {
switch (this.state) {
case NULL:
case PROPOSED:
case SESSION_INITIALIZED:
case NULL, PROPOSED, SESSION_INITIALIZED -> {
if (isInitiator()) {
return RtpEndUserState.RINGING;
} else {
return RtpEndUserState.INCOMING_CALL;
}
case PROCEED:
}
case PROCEED -> {
if (isInitiator()) {
return RtpEndUserState.RINGING;
} else {
return RtpEndUserState.ACCEPTING_CALL;
}
case SESSION_INITIALIZED_PRE_APPROVED:
}
case SESSION_INITIALIZED_PRE_APPROVED -> {
if (isInitiator()) {
return RtpEndUserState.RINGING;
} else {
return RtpEndUserState.CONNECTING;
}
case SESSION_ACCEPTED:
}
case SESSION_ACCEPTED -> {
final ContentAddition ca = getPendingContentAddition();
if (ca != null && ca.direction == ContentAddition.Direction.INCOMING) {
return RtpEndUserState.INCOMING_CONTENT_ADD;
}
return getPeerConnectionStateAsEndUserState();
case REJECTED:
case REJECTED_RACED:
case TERMINATED_DECLINED_OR_BUSY:
}
case REJECTED, REJECTED_RACED, TERMINATED_DECLINED_OR_BUSY -> {
if (isInitiator()) {
return RtpEndUserState.DECLINED_OR_BUSY;
} else {
return RtpEndUserState.ENDED;
}
case TERMINATED_SUCCESS:
case ACCEPTED:
case RETRACTED:
case TERMINATED_CANCEL_OR_TIMEOUT:
}
case TERMINATED_SUCCESS, ACCEPTED, RETRACTED, TERMINATED_CANCEL_OR_TIMEOUT -> {
return RtpEndUserState.ENDED;
case RETRACTED_RACED:
}
case RETRACTED_RACED -> {
if (isInitiator()) {
return RtpEndUserState.ENDED;
} else {
return RtpEndUserState.RETRACTED;
}
case TERMINATED_CONNECTIVITY_ERROR:
}
case TERMINATED_CONNECTIVITY_ERROR -> {
return zeroDuration()
? RtpEndUserState.CONNECTIVITY_ERROR
: RtpEndUserState.CONNECTIVITY_LOST_ERROR;
case TERMINATED_APPLICATION_FAILURE:
}
case TERMINATED_APPLICATION_FAILURE -> {
return RtpEndUserState.APPLICATION_ERROR;
case TERMINATED_SECURITY_ERROR:
}
case TERMINATED_SECURITY_ERROR -> {
return RtpEndUserState.SECURITY_ERROR;
}
}
throw new IllegalStateException(
String.format("%s has no equivalent EndUserState", this.state));
@ -2237,19 +2275,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
// be in SESSION_ACCEPTED even though the peerConnection has been torn down
return RtpEndUserState.ENDING_CALL;
}
switch (state) {
case CONNECTED:
return RtpEndUserState.CONNECTED;
case NEW:
case CONNECTING:
return RtpEndUserState.CONNECTING;
case CLOSED:
return RtpEndUserState.ENDING_CALL;
default:
return zeroDuration()
? RtpEndUserState.CONNECTIVITY_ERROR
: RtpEndUserState.RECONNECTING;
}
return switch (state) {
case CONNECTED -> RtpEndUserState.CONNECTED;
case NEW, CONNECTING -> RtpEndUserState.CONNECTING;
case CLOSED -> RtpEndUserState.ENDING_CALL;
default -> zeroDuration()
? RtpEndUserState.CONNECTIVITY_ERROR
: RtpEndUserState.RECONNECTING;
};
}
public ContentAddition getPendingContentAddition() {
@ -2284,9 +2317,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
} else if (initiatorContentMap != null) {
return initiatorContentMap.getMedia();
} else if (isTerminated()) {
return Collections.emptySet(); //we might fail before we ever got a chance to set media
return Collections.emptySet(); // we might fail before we ever got a chance to set media
} else {
return Preconditions.checkNotNull(this.proposedMedia, "RTP connection has not been initialized properly");
return Preconditions.checkNotNull(
this.proposedMedia, "RTP connection has not been initialized properly");
}
}
@ -2306,35 +2340,29 @@ public class JingleRtpConnection extends AbstractJingleConnection
throw new IllegalStateException(String.format("%s has already been proposed", media));
}
// TODO add state protection - can only add while ACCEPTED or so
Log.d(Config.LOGTAG,"adding media: "+media);
Log.d(Config.LOGTAG, "adding media: " + media);
return webRTCWrapper.addTrack(media);
}
public synchronized void acceptCall() {
switch (this.state) {
case PROPOSED:
case PROPOSED -> {
cancelRingingTimeout();
acceptCallFromProposed();
break;
case SESSION_INITIALIZED:
}
case SESSION_INITIALIZED -> {
cancelRingingTimeout();
acceptCallFromSessionInitialized();
break;
case ACCEPTED:
Log.w(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": the call has already been accepted with another client. UI was just lagging behind");
break;
case PROCEED:
case SESSION_ACCEPTED:
Log.w(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": the call has already been accepted. user probably double tapped the UI");
break;
default:
throw new IllegalStateException("Can not accept call from " + this.state);
}
case ACCEPTED -> Log.w(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": the call has already been accepted with another client. UI was just lagging behind");
case PROCEED, SESSION_ACCEPTED -> Log.w(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": the call has already been accepted. user probably double tapped the UI");
default -> throw new IllegalStateException("Can not accept call from " + this.state);
}
}
@ -2356,14 +2384,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
return;
}
switch (this.state) {
case PROPOSED:
rejectCallFromProposed();
break;
case SESSION_INITIALIZED:
rejectCallFromSessionInitiate();
break;
default:
throw new IllegalStateException("Can not reject call from " + this.state);
case PROPOSED -> rejectCallFromProposed();
case SESSION_INITIALIZED -> rejectCallFromSessionInitiate();
default -> throw new IllegalStateException("Can not reject call from " + this.state);
}
}
@ -2428,9 +2451,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
finish();
}
private void setupWebRTC(final Set<Media> media, final List<PeerConnection.IceServer> iceServers, final boolean trickle) throws WebRTCWrapper.InitializationException {
private void setupWebRTC(
final Set<Media> media,
final List<PeerConnection.IceServer> iceServers,
final boolean trickle)
throws WebRTCWrapper.InitializationException {
this.jingleConnectionManager.ensureConnectionIsRegistered(this);
this.webRTCWrapper.setup(this.xmppConnectionService, AppRTCAudioManager.SpeakerPhonePreference.of(media));
this.webRTCWrapper.setup(
this.xmppConnectionService, AppRTCAudioManager.SpeakerPhonePreference.of(media));
this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
}
@ -2606,7 +2634,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
final Throwable cause = Throwables.getRootCause(e);
webRTCWrapper.close();
if (isTerminated()) {
Log.d(Config.LOGTAG, "failed to renegotiate. session was already terminated", cause);
Log.d(
Config.LOGTAG,
"failed to renegotiate. session was already terminated",
cause);
return;
}
Log.d(Config.LOGTAG, "failed to renegotiate. sending session-terminate", cause);
@ -2691,7 +2722,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
prepareOutgoingContentMap(contentAdd);
Futures.addCallback(
outgoingContentMapFuture,
new FutureCallback<RtpContentMap>() {
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap outgoingContentMap) {
sendContentAdd(outgoingContentMap);
@ -2889,9 +2920,6 @@ public class JingleRtpConnection extends AbstractJingleConnection
continue;
}
if (Arrays.asList("stun", "stuns", "turn", "turns")
.contains(type)
&& Arrays.asList("udp", "tcp").contains(transport)) {
@ -2906,15 +2934,19 @@ public class JingleRtpConnection extends AbstractJingleConnection
// STUN URLs do not support a query section since M110
final String uri;
if (Arrays.asList("stun","stuns").contains(type)) {
uri = String.format("%s:%s:%s", type, IP.wrapIPv6(host),port);
if (Arrays.asList("stun", "stuns").contains(type)) {
uri =
String.format(
"%s:%s:%s",
type, IP.wrapIPv6(host), port);
} else {
uri = String.format(
"%s:%s:%s?transport=%s",
type,
IP.wrapIPv6(host),
port,
transport);
uri =
String.format(
"%s:%s:%s?transport=%s",
type,
IP.wrapIPv6(host),
port,
transport);
}
final PeerConnection.IceServer.Builder iceServerBuilder =