From fd4b8ba1885a9f6e24a87e47c3a6a730f9ed15f8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 3 Oct 2023 12:55:44 +0200 Subject: [PATCH] bring back ICE Renomination via negotiation --- .../eu/siacs/conversations/xml/Namespace.java | 1 + .../xmpp/jingle/JingleRtpConnection.java | 12 +++++ .../xmpp/jingle/SessionDescription.java | 27 +++++++---- .../jingle/stanzas/IceUdpTransportInfo.java | 45 +++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index b614251bd..201b11894 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -65,5 +65,6 @@ public final class Namespace { public static final String PARS = "urn:xmpp:pars:0"; public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; + public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 8aea0da9e..766482899 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -15,6 +15,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.FutureCallback; @@ -237,6 +238,9 @@ public class JingleRtpConnection extends AbstractJingleConnection case CONTENT_REMOVE: receiveContentRemove(jinglePacket); break; + case CONTENT_MODIFY: + receiveContentModify(jinglePacket); + break; default: respondOk(jinglePacket); Log.d( @@ -507,6 +511,14 @@ public class JingleRtpConnection extends AbstractJingleConnection + ContentAddition.summary(receivedContentAccept)); } + private void receiveContentModify(final JinglePacket jinglePacket) { + final Map modification = + Maps.transformEntries( + jinglePacket.getJingleContents(), (key, value) -> value.getSenders()); + respondOk(jinglePacket); + Log.d(Config.LOGTAG, "receiveContentModification(" + modification + ")"); + } + private void receiveContentReject(final JinglePacket jinglePacket) { final RtpContentMap receivedContentReject; try { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index 77b598793..b9d99fe6e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -11,23 +11,26 @@ import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + public class SessionDescription { public static final String LINE_DIVIDER = "\r\n"; private static final String HARDCODED_MEDIA_PROTOCOL = "UDP/TLS/RTP/SAVPF"; // probably only true for DTLS-SRTP aka when we have a fingerprint private static final int HARDCODED_MEDIA_PORT = 9; - private static final String HARDCODED_ICE_OPTIONS = "trickle"; + private static final Collection HARDCODED_ICE_OPTIONS = + Collections.singleton("trickle"); private static final String HARDCODED_CONNECTION = "IN IP4 0.0.0.0"; public final int version; @@ -128,7 +131,8 @@ public class SessionDescription { return sessionDescriptionBuilder.createSessionDescription(); } - public static SessionDescription of(final RtpContentMap contentMap, final boolean isInitiatorContentMap) { + public static SessionDescription of( + final RtpContentMap contentMap, final boolean isInitiatorContentMap) { final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder(); final ArrayListMultimap attributeMap = ArrayListMultimap.create(); final ImmutableList.Builder mediaListBuilder = new ImmutableList.Builder<>(); @@ -166,7 +170,10 @@ public class SessionDescription { } checkNoWhitespace(pwd, "pwd value must not contain any whitespaces"); mediaAttributes.put("ice-pwd", pwd); - mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS); + final List negotiatedIceOptions = transport.getIceOptions(); + final Collection iceOptions = + negotiatedIceOptions.isEmpty() ? HARDCODED_ICE_OPTIONS : negotiatedIceOptions; + mediaAttributes.put("ice-options", Joiner.on(' ').join(iceOptions)); final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint(); if (fingerprint != null) { mediaAttributes.put( @@ -291,13 +298,15 @@ public class SessionDescription { throw new IllegalArgumentException( "A source specific media attribute is missing its value"); } - mediaAttributes.put("ssrc", id + " " + parameterName + ":" + parameterValue.trim()); + mediaAttributes.put( + "ssrc", id + " " + parameterName + ":" + parameterValue.trim()); } } mediaAttributes.put("mid", name); - mediaAttributes.put(descriptionTransport.senders.asMediaAttribute(isInitiatorContentMap), ""); + mediaAttributes.put( + descriptionTransport.senders.asMediaAttribute(isInitiatorContentMap), ""); if (description.hasChild("rtcp-mux", Namespace.JINGLE_APPS_RTP) || group != null) { mediaAttributes.put("rtcp-mux", ""); } 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 432333090..bd7e44501 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 @@ -1,17 +1,23 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; +import android.util.Log; + import androidx.annotation.NonNull; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; @@ -20,6 +26,7 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.SessionDescription; @@ -59,6 +66,9 @@ public class IceUdpTransportInfo extends GenericTransportInfo { if (fingerprint != null) { iceUdpTransportInfo.addChild(fingerprint); } + for (final String iceOption : IceOption.of(media)) { + iceUdpTransportInfo.addChild(new IceOption(iceOption)); + } return iceUdpTransportInfo; } @@ -76,6 +86,16 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return fingerprint == null ? null : Fingerprint.upgrade(fingerprint); } + public List getIceOptions() { + final ImmutableList.Builder optionBuilder = new ImmutableList.Builder<>(); + for(final Element child : this.children) { + if (Namespace.JINGLE_TRANSPORT_ICE_OPTION.equals(child.getNamespace()) && IceOption.WELL_KNOWN.contains(child.getName())) { + optionBuilder.add(child.getName()); + } + } + return optionBuilder.build(); + } + public Credentials getCredentials() { final String ufrag = this.getAttribute("ufrag"); final String password = this.getAttribute("pwd"); @@ -408,4 +428,29 @@ public class IceUdpTransportInfo extends GenericTransportInfo { throw new IllegalStateException(this.name() + " can not be flipped"); } } + + public static class IceOption extends Element { + + public static final List WELL_KNOWN = Arrays.asList("trickle", "renomination"); + + public IceOption(final String name) { + super(name, Namespace.JINGLE_TRANSPORT_ICE_OPTION); + } + + public static Collection of(SessionDescription.Media media) { + final String iceOptions = Iterables.getFirst(media.attributes.get("ice-options"), null); + if (Strings.isNullOrEmpty(iceOptions)) { + return Collections.emptyList(); + } + final ImmutableList.Builder optionBuilder = new ImmutableList.Builder<>(); + for (final String iceOption : Splitter.on(' ').split(iceOptions)) { + if (WELL_KNOWN.contains(iceOption)) { + optionBuilder.add(iceOption); + } else { + Log.w(Config.LOGTAG, "unrecognized ice option: " + iceOption); + } + } + return optionBuilder.build(); + } + } }