diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index f9c245b0e..7af1469cf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/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 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 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 existingContentIds = this.contents.keySet(); final Set 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 combined = + new ImmutableMap.Builder() + .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); + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index de26f1afc..53b7de1e0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/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; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index ee8d12b70..432333090 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/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"); } } }