sdp candidate to transport-info

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java         | 60 
src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java               | 16 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java | 42 
3 files changed, 112 insertions(+), 6 deletions(-)

Detailed changes

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

@@ -42,6 +42,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
     }
 
     private State state = State.NULL;
+    private RtpContentMap initialRtpContentMap;
 
 
     public JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
@@ -152,9 +153,32 @@ public class JingleRtpConnection extends AbstractJingleConnection {
     }
 
     private void sendSessionInitiate(RtpContentMap rtpContentMap) {
-        Log.d(Config.LOGTAG, rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId).toString());
+        this.initialRtpContentMap = rtpContentMap;
+        final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
+        Log.d(Config.LOGTAG, sessionInitiate.toString());
+        send(sessionInitiate);
     }
 
+    private void sendTransportInfo(final String contentName, IceUdpTransportInfo.Candidate candidate) {
+        final RtpContentMap transportInfo;
+        try {
+            transportInfo = this.initialRtpContentMap.transportInfo(contentName, candidate);
+        } catch (Exception e) {
+            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to prepare transport-info from candidate for content=" + contentName);
+            return;
+        }
+        final JinglePacket jinglePacket = transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
+        Log.d(Config.LOGTAG, jinglePacket.toString());
+        send(jinglePacket);
+    }
+
+    private void send(final JinglePacket jinglePacket) {
+        jinglePacket.setTo(id.with);
+        //TODO track errors
+        xmppConnectionService.sendIqPacket(id.account, jinglePacket, null);
+    }
+
+
     private void sendSessionAccept() {
         Log.d(Config.LOGTAG, "sending session-accept");
     }
@@ -186,7 +210,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
         stream.addTrack(audioTrack);
 
 
-        PeerConnection peer = peerConnectionFactory.createPeerConnection(Collections.emptyList(), new PeerConnection.Observer() {
+        PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(Collections.emptyList(), new PeerConnection.Observer() {
             @Override
             public void onSignalingChange(PeerConnection.SignalingState signalingState) {
 
@@ -204,11 +228,16 @@ public class JingleRtpConnection extends AbstractJingleConnection {
 
             @Override
             public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
-
+                Log.d(Config.LOGTAG, "onIceGatheringChange() " + iceGatheringState);
             }
 
             @Override
             public void onIceCandidate(IceCandidate iceCandidate) {
+                IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp);
+                Log.d(Config.LOGTAG, "onIceCandidate: " + iceCandidate.sdp);
+                Log.d(Config.LOGTAG, "xml: " + candidate.toString());
+                Log.d(Config.LOGTAG, "mid: " + iceCandidate.sdpMid);
+                sendTransportInfo(iceCandidate.sdpMid, candidate);
 
             }
 
@@ -243,9 +272,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
             }
         });
 
-        peer.addStream(stream);
+        peerConnection.addStream(stream);
 
-        peer.createOffer(new SdpObserver() {
+        peerConnection.createOffer(new SdpObserver() {
 
             @Override
             public void onCreateSuccess(org.webrtc.SessionDescription description) {
@@ -253,6 +282,27 @@ public class JingleRtpConnection extends AbstractJingleConnection {
                 Log.d(Config.LOGTAG, "description: " + description.description);
                 final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
                 sendSessionInitiate(rtpContentMap);
+                peerConnection.setLocalDescription(new SdpObserver() {
+                    @Override
+                    public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
+
+                    }
+
+                    @Override
+                    public void onSetSuccess() {
+                        Log.d(Config.LOGTAG, "onSetSuccess()");
+                    }
+
+                    @Override
+                    public void onCreateFailure(String s) {
+
+                    }
+
+                    @Override
+                    public void onSetFailure(String s) {
+
+                    }
+                }, description);
             }
 
             @Override

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

@@ -54,13 +54,27 @@ public class RtpContentMap {
         }
         for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
             final Content content = new Content(Content.Creator.INITIATOR, entry.getKey());
-            content.addChild(entry.getValue().description);
+            if (entry.getValue().description != null) {
+                content.addChild(entry.getValue().description);
+            }
             content.addChild(entry.getValue().transport);
             jinglePacket.addJingleContent(content);
         }
         return jinglePacket;
     }
 
+    public RtpContentMap transportInfo(final String contentName, final IceUdpTransportInfo.Candidate candidate) {
+        final RtpContentMap.DescriptionTransport descriptionTransport =  contents.get(contentName);
+        final IceUdpTransportInfo transportInfo = descriptionTransport == null ? null : descriptionTransport.transport;
+        if (transportInfo == null) {
+            throw new IllegalArgumentException("Unable to find transport info for content name "+contentName);
+        }
+        final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper();
+        newTransportInfo.addChild(candidate);
+        return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null,newTransportInfo)));
+
+    }
+
     public static class DescriptionTransport {
         public final RtpDescription description;
         public final IceUdpTransportInfo transport;

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

@@ -1,12 +1,17 @@
 package eu.siacs.conversations.xmpp.jingle.stanzas;
 
+import android.util.Log;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
+import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.List;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.jingle.SessionDescription;
@@ -41,6 +46,12 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
         return transportInfo;
     }
 
+    public IceUdpTransportInfo cloneWrapper() {
+        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
+        transportInfo.setAttributes(new Hashtable<>(getAttributes()));
+        return transportInfo;
+    }
+
     public static IceUdpTransportInfo of(SessionDescription sessionDescription, SessionDescription.Media media) {
         final String ufrag = Iterables.getFirst(media.attributes.get("ice-ufrag"), null);
         final String pwd = Iterables.getFirst(media.attributes.get("ice-pwd"), null);
@@ -132,6 +143,37 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             candidate.setChildren(element.getChildren());
             return candidate;
         }
+
+        // https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-5.1
+        public static Candidate fromSdpAttribute(final String attribute) {
+            final String[] pair = attribute.split(":", 2);
+            if (pair.length == 2 && "candidate".equals(pair[0])) {
+                final String[] segments = pair[1].split(" ");
+                if (segments.length >= 6) {
+                    final String foundation = segments[0];
+                    final String component = segments[1];
+                    final String transport = segments[2];
+                    final String priority = segments[3];
+                    final String connectionAddress = segments[4];
+                    final String port = segments[5];
+                    final HashMap<String, String> additional = new HashMap<>();
+                    for (int i = 6; i < segments.length - 1; i = i + 2) {
+                        additional.put(segments[i], segments[i + 1]);
+                    }
+                    final Candidate candidate = new Candidate();
+                    candidate.setAttribute("component", component);
+                    candidate.setAttribute("foundation", foundation);
+                    candidate.setAttribute("generation", additional.get("generation"));
+                    candidate.setAttribute("ip", connectionAddress);
+                    candidate.setAttribute("port", port);
+                    candidate.setAttribute("priority", priority);
+                    candidate.setAttribute("protocol", transport);
+                    candidate.setAttribute("type", additional.get("typ"));
+                    return candidate;
+                }
+            }
+            return null;
+        }
     }