add helper methods for content modification to RtpContentMap

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java               | 80 
src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java               |  9 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java | 70 
3 files changed, 137 insertions(+), 22 deletions(-)

Detailed changes

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

@@ -1,7 +1,9 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
@@ -236,6 +238,23 @@ public class RtpContentMap {
         throw new IllegalStateException("Content map doesn't have distinct DTLS setup");
     }
 
+    private DTLS getDistinctDtls() {
+        final Set<DTLS> dtlsSet =
+                ImmutableSet.copyOf(
+                        Collections2.transform(
+                                contents.values(),
+                                dt -> {
+                                    final IceUdpTransportInfo.Fingerprint fp =
+                                            dt.transport.getFingerprint();
+                                    return new DTLS(fp.getHash(), fp.getSetup(), fp.getContent());
+                                }));
+        final DTLS dtls = Iterables.getFirst(dtlsSet, null);
+        if (dtlsSet.size() == 1 && dtls != null) {
+            return dtls;
+        }
+        throw new IllegalStateException("Content map doesn't have distinct DTLS setup");
+    }
+
     public boolean emptyCandidates() {
         int count = 0;
         for (DescriptionTransport descriptionTransport : contents.values()) {
@@ -262,12 +281,22 @@ public class RtpContentMap {
         return new RtpContentMap(this.group, contentMapBuilder.build());
     }
 
+    public RtpContentMap toContentModification(final Collection<String> modifications) {
+        return new RtpContentMap(
+                this.group,
+                Maps.transformValues(
+                        Maps.filterKeys(contents, Predicates.in(modifications)),
+                        dt ->
+                                new DescriptionTransport(
+                                        dt.senders, dt.description, IceUdpTransportInfo.STUB)));
+    }
+
     public Diff diff(final RtpContentMap rtpContentMap) {
         final Set<String> existingContentIds = this.contents.keySet();
         final Set<String> newContentIds = rtpContentMap.contents.keySet();
         return new Diff(
-                Sets.difference(newContentIds, existingContentIds),
-                Sets.difference(existingContentIds, newContentIds));
+                ImmutableSet.copyOf(Sets.difference(newContentIds, existingContentIds)),
+                ImmutableSet.copyOf(Sets.difference(existingContentIds, newContentIds)));
     }
 
     public boolean iceRestart(final RtpContentMap rtpContentMap) {
@@ -278,6 +307,26 @@ public class RtpContentMap {
         }
     }
 
+    public RtpContentMap addContent(final RtpContentMap modification) {
+        final IceUdpTransportInfo.Credentials credentials = getDistinctCredentials();
+        final DTLS dtls = getDistinctDtls();
+        final IceUdpTransportInfo iceUdpTransportInfo =
+                IceUdpTransportInfo.of(credentials, dtls.setup, dtls.hash, dtls.fingerprint);
+        final Map<String, DescriptionTransport> combined =
+                new ImmutableMap.Builder<String, DescriptionTransport>()
+                        .putAll(contents)
+                        .putAll(
+                                Maps.transformValues(
+                                        modification.contents,
+                                        dt ->
+                                                new DescriptionTransport(
+                                                        dt.senders,
+                                                        dt.description,
+                                                        iceUdpTransportInfo)))
+                        .build();
+        return new RtpContentMap(modification.group, combined);
+    }
+
     public static class DescriptionTransport {
         public final Content.Senders senders;
         public final RtpDescription description;
@@ -370,4 +419,31 @@ public class RtpContentMap {
                     .toString();
         }
     }
+
+    public static final class DTLS {
+        public final String hash;
+        public final IceUdpTransportInfo.Setup setup;
+        public final String fingerprint;
+
+        private DTLS(String hash, IceUdpTransportInfo.Setup setup, String fingerprint) {
+            this.hash = hash;
+            this.setup = setup;
+            this.fingerprint = fingerprint;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DTLS dtls = (DTLS) o;
+            return Objects.equal(hash, dtls.hash)
+                    && setup == dtls.setup
+                    && Objects.equal(fingerprint, dtls.fingerprint);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(hash, setup, fingerprint);
+        }
+    }
 }

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

@@ -186,15 +186,22 @@ public class WebRTCWrapper {
                 }
 
                 @Override
-                public void onTrack(RtpTransceiver transceiver) {
+                public void onTrack(final RtpTransceiver transceiver) {
                     Log.d(
                             EXTENDED_LOGGING_TAG,
                             "onTrack(mid="
                                     + transceiver.getMid()
                                     + ",media="
                                     + transceiver.getMediaType()
+                                    + ",direction="
+                                    + transceiver.getDirection()
                                     + ")");
                 }
+
+                @Override
+                public void onRemoveTrack(final RtpReceiver receiver) {
+                    Log.d(EXTENDED_LOGGING_TAG, "onRemoveTrack(" + receiver.id() + ")");
+                }
             };
     @Nullable private PeerConnectionFactory peerConnectionFactory = null;
     @Nullable private PeerConnection peerConnection = null;

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

@@ -12,8 +12,6 @@ import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
@@ -28,23 +26,29 @@ import eu.siacs.conversations.xmpp.jingle.SessionDescription;
 
 public class IceUdpTransportInfo extends GenericTransportInfo {
 
+    public static final IceUdpTransportInfo STUB = new IceUdpTransportInfo();
+
     public IceUdpTransportInfo() {
         super("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
     }
 
     public static IceUdpTransportInfo upgrade(final Element element) {
-        Preconditions.checkArgument("transport".equals(element.getName()), "Name of provided element is not transport");
-        Preconditions.checkArgument(Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(element.getNamespace()), "Element does not match ice-udp transport namespace");
+        Preconditions.checkArgument(
+                "transport".equals(element.getName()), "Name of provided element is not transport");
+        Preconditions.checkArgument(
+                Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(element.getNamespace()),
+                "Element does not match ice-udp transport namespace");
         final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
         transportInfo.setAttributes(element.getAttributes());
         transportInfo.setChildren(element.getChildren());
         return transportInfo;
     }
 
-    public static IceUdpTransportInfo of(SessionDescription sessionDescription, SessionDescription.Media media) {
+    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);
-        IceUdpTransportInfo iceUdpTransportInfo = new IceUdpTransportInfo();
+        final IceUdpTransportInfo iceUdpTransportInfo = new IceUdpTransportInfo();
         if (ufrag != null) {
             iceUdpTransportInfo.setAttribute("ufrag", ufrag);
         }
@@ -56,7 +60,15 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             iceUdpTransportInfo.addChild(fingerprint);
         }
         return iceUdpTransportInfo;
+    }
 
+    public static IceUdpTransportInfo of(
+            final Credentials credentials,  final Setup setup, final String hash, final String fingerprint) {
+        final IceUdpTransportInfo iceUdpTransportInfo = new IceUdpTransportInfo();
+        iceUdpTransportInfo.addChild(Fingerprint.of(setup, hash, fingerprint));
+        iceUdpTransportInfo.setAttribute("ufrag", credentials.ufrag);
+        iceUdpTransportInfo.setAttribute("pwd", credentials.password);
+        return iceUdpTransportInfo;
     }
 
     public Fingerprint getFingerprint() {
@@ -91,7 +103,8 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
         transportInfo.setAttribute("ufrag", credentials.ufrag);
         transportInfo.setAttribute("pwd", credentials.password);
         for (final Element child : getChildren()) {
-            if (child.getName().equals("fingerprint") && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
+            if (child.getName().equals("fingerprint")
+                    && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
                 final Fingerprint fingerprint = new Fingerprint();
                 fingerprint.setAttributes(new Hashtable<>(child.getAttributes()));
                 fingerprint.setContent(child.getContent());
@@ -231,7 +244,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             return getAttributeAsInt("rel-port");
         }
 
-        public String getType() { //TODO might be converted to enum
+        public String getType() { // TODO might be converted to enum
             return getAttribute("type");
         }
 
@@ -256,7 +269,8 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             checkNotNullNoWhitespace(protocol, "protocol");
             final String transport = protocol.toLowerCase(Locale.ROOT);
             if (!"udp".equals(transport)) {
-                throw new IllegalArgumentException(String.format("'%s' is not a supported protocol", transport));
+                throw new IllegalArgumentException(
+                        String.format("'%s' is not a supported protocol", transport));
             }
             final String priority = this.getAttribute("priority");
             checkNotNullNoWhitespace(priority, "priority");
@@ -284,7 +298,15 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             if (ufrag != null) {
                 additionalParameter.put("ufrag", ufrag);
             }
-            final String parametersString = Joiner.on(' ').join(Collections2.transform(additionalParameter.entrySet(), input -> String.format("%s %s", input.getKey(), input.getValue())));
+            final String parametersString =
+                    Joiner.on(' ')
+                            .join(
+                                    Collections2.transform(
+                                            additionalParameter.entrySet(),
+                                            input ->
+                                                    String.format(
+                                                            "%s %s",
+                                                            input.getKey(), input.getValue())));
             return String.format(
                     "candidate:%s %s %s %s %s %s %s",
                     foundation,
@@ -293,20 +315,19 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
                     priority,
                     connectionAddress,
                     port,
-                    parametersString
-
-            );
+                    parametersString);
         }
     }
 
     private static void checkNotNullNoWhitespace(final String value, final String name) {
         if (Strings.isNullOrEmpty(value)) {
-            throw new IllegalArgumentException(String.format("Parameter %s is missing or empty", name));
+            throw new IllegalArgumentException(
+                    String.format("Parameter %s is missing or empty", name));
         }
-        SessionDescription.checkNoWhitespace(value, String.format("Parameter %s contains white spaces", name));
+        SessionDescription.checkNoWhitespace(
+                value, String.format("Parameter %s contains white spaces", name));
     }
 
-
     public static class Fingerprint extends Element {
 
         private Fingerprint() {
@@ -340,11 +361,20 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             return null;
         }
 
-        public static Fingerprint of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
+        public static Fingerprint of(
+                final SessionDescription sessionDescription, final SessionDescription.Media media) {
             final Fingerprint fingerprint = of(media.attributes);
             return fingerprint == null ? of(sessionDescription.attributes) : fingerprint;
         }
 
+        private static Fingerprint of(final Setup setup, final String hash, final String content) {
+            final Fingerprint fingerprint = new Fingerprint();
+            fingerprint.setContent(content);
+            fingerprint.setAttribute("hash", hash);
+            fingerprint.setAttribute("setup", setup.toString().toLowerCase(Locale.ROOT));
+            return fingerprint;
+        }
+
         public String getHash() {
             return this.getAttribute("hash");
         }
@@ -356,7 +386,9 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
     }
 
     public enum Setup {
-        ACTPASS, PASSIVE, ACTIVE;
+        ACTPASS,
+        PASSIVE,
+        ACTIVE;
 
         public static Setup of(String setup) {
             try {
@@ -373,7 +405,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             if (this == ACTIVE) {
                 return PASSIVE;
             }
-            throw new IllegalStateException(this.name()+" can not be flipped");
+            throw new IllegalStateException(this.name() + " can not be flipped");
         }
     }
 }