upgrade to 'both' upon accepting recvonly content-add

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java | 59 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java     |  5 
2 files changed, 49 insertions(+), 15 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -65,6 +65,7 @@ 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;
@@ -361,23 +362,32 @@ public class JingleRtpConnection extends AbstractJingleConnection
             final JinglePacket jinglePacket, final RtpContentMap contentMap) {
         final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates =
                 contentMap.contents.entrySet();
-        if (this.state == State.SESSION_ACCEPTED) {
-            // zero candidates + modified credentials are an ICE restart offer
-            if (checkForIceRestart(jinglePacket, contentMap)) {
-                return;
-            }
+        final RtpContentMap remote = getRemoteContentMap();
+        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+")");
             respondOk(jinglePacket);
-            try {
-                processCandidates(candidates);
-            } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) {
-                Log.w(
-                        Config.LOGTAG,
-                        id.account.getJid().asBareJid()
-                                + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored");
-            }
-        } else {
+            pendingIceCandidates.addAll(candidates);
+            return;
+        }
+        if (this.state != State.SESSION_ACCEPTED) {
+            Log.d(Config.LOGTAG,"received transport-info prematurely. adding to backlog");
             respondOk(jinglePacket);
             pendingIceCandidates.addAll(candidates);
+            return;
+        }
+        // zero candidates + modified credentials are an ICE restart offer
+        if (checkForIceRestart(jinglePacket, contentMap)) {
+            return;
+        }
+        respondOk(jinglePacket);
+        try {
+            processCandidates(candidates);
+        } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) {
+            Log.w(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored");
         }
     }
 
@@ -781,7 +791,23 @@ public class JingleRtpConnection extends AbstractJingleConnection
 
         if (contentAddition.equals(ContentAddition.summary(incomingContentAdd))) {
             this.incomingContentAdd = null;
-            acceptContentAdd(contentAddition, incomingContentAdd);
+            final Set<Content.Senders> senders = incomingContentAdd.getSenders();
+            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);
+                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);
+                    }
+                });
+            }
         } else {
             throw new IllegalStateException("Accepted content add does not match pending content-add");
         }
@@ -832,6 +858,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
                     id.getAccount().getJid().asBareJid()
                             + ": sending content-accept "
                             + ContentAddition.summary(contentAcceptMap));
+
+            addIceCandidatesFromBlackLog();
+
             modifyLocalContentMap(rtpContentMap);
             sendContentAccept(contentAcceptMap);
             this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java 🔗

@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.Locale;
 import java.util.Set;
@@ -147,6 +148,10 @@ public class Content extends Element {
             return BOTH;
         }
 
+        public static Set<Senders> receiveOnly(final boolean initiator) {
+            return ImmutableSet.of(initiator ? RESPONDER : INITIATOR);
+        }
+
         @Override
         @NonNull
         public String toString() {