Detailed changes
@@ -36,6 +36,7 @@ public final class Namespace {
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
public static final String JINGLE_APPS_DTLS = "urn:xmpp:jingle:apps:dtls:0";
+ public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
@@ -592,7 +592,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
content.setTransport(new S5BTransportInfo(this.transportId, candidates));
Log.d(Config.LOGTAG, String.format("%s: sending S5B offer with %d candidates", id.account.getJid().asBareJid(), candidates.size()));
}
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sendJinglePacket(packet, (account, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": other party received offer");
@@ -617,7 +617,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
hash.setAttribute("algo", "sha-1").setContent(Base64.encodeToString(file.getSha1Sum(), Base64.NO_WRAP));
final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INFO);
- packet.setJingleChild(checksum);
+ packet.addJingleChild(checksum);
this.sendJinglePacket(packet);
}
@@ -651,7 +651,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
public void failed() {
Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
sendJinglePacket(packet);
connectNextCandidate();
}
@@ -661,7 +661,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
Log.d(Config.LOGTAG, "connected to proxy65 candidate");
mergeCandidate(candidate);
content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
sendJinglePacket(packet);
connectNextCandidate();
}
@@ -669,7 +669,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
} else {
Log.d(Config.LOGTAG, "did not find a proxy65 candidate for ourselves");
content.setTransport(new S5BTransportInfo(transportId, getOurCandidates()));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
sendJinglePacket(packet);
connectNextCandidate();
}
@@ -682,7 +682,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
final Content content = new Content(contentCreator, contentName);
content.setDescription(this.description);
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.transport.receive(file, onFileTransmissionStatusChanged);
this.sendJinglePacket(packet);
}
@@ -909,7 +909,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
Content content = new Content(this.contentCreator, this.contentName);
this.transportId = JingleConnectionManager.nextRandomId();
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sendJinglePacket(packet);
}
@@ -941,7 +941,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
final Content content = new Content(contentCreator, contentName);
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
- answer.setJingleContent(content);
+ answer.addJingleContent(content);
respondToIq(packet, true);
@@ -1122,7 +1122,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransport(new S5BTransportInfo(this.transportId, new Element("activated").setAttribute("cid", cid)));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sendJinglePacket(packet);
}
@@ -1130,7 +1130,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransport(new S5BTransportInfo(this.transportId, new Element("proxy-error")));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sendJinglePacket(packet);
}
@@ -1138,7 +1138,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-used").setAttribute("cid", cid)));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sentCandidate = true;
if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
connect();
@@ -1151,7 +1151,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
Content content = new Content(this.contentCreator, this.contentName);
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-error")));
- packet.setJingleContent(content);
+ packet.addJingleContent(content);
this.sentCandidate = true;
this.sendJinglePacket(packet);
if (receivedCandidate && mJingleStatus == JINGLE_STATUS_ACCEPTED) {
@@ -2,12 +2,9 @@ package eu.siacs.conversations.xmpp.jingle;
import android.util.Log;
-import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.DataChannel;
@@ -26,9 +23,6 @@ import java.util.Map;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
-import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
-import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
@@ -73,18 +67,18 @@ public class JingleRtpConnection extends AbstractJingleConnection {
//TODO respond with out-of-order
return;
}
- final Map<String, DescriptionTransport> contents;
+ final RtpContentMap contentMap;
try {
- contents = DescriptionTransport.of(jinglePacket.getJingleContents());
+ contentMap = RtpContentMap.of(jinglePacket);
} catch (IllegalArgumentException | NullPointerException e) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
return;
}
- Log.d(Config.LOGTAG, "processing session-init with " + contents.size() + " contents");
+ Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents");
final State oldState = this.state;
if (transition(State.SESSION_INITIALIZED)) {
if (oldState == State.PROCEED) {
- processContents(contents);
+ processContents(contentMap);
sendSessionAccept();
} else {
//TODO start ringing
@@ -94,9 +88,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
}
}
- private void processContents(final Map<String, DescriptionTransport> contents) {
- for (Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
- final DescriptionTransport descriptionTransport = content.getValue();
+ private void processContents(final RtpContentMap contentMap) {
+ for (Map.Entry<String, RtpContentMap.DescriptionTransport> content : contentMap.contents.entrySet()) {
+ final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
final RtpDescription rtpDescription = descriptionTransport.description;
Log.d(Config.LOGTAG, "receive content with name " + content.getKey() + " and media=" + rtpDescription.getMedia());
for (RtpDescription.PayloadType payloadType : rtpDescription.getPayloadTypes()) {
@@ -154,7 +148,11 @@ public class JingleRtpConnection extends AbstractJingleConnection {
private void sendSessionInitiate() {
setupWebRTC();
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending session-initiate");
+ Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate");
+ }
+
+ private void sendSessionInitiate(RtpContentMap rtpContentMap) {
+ Log.d(Config.LOGTAG, rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId).toString());
}
private void sendSessionAccept() {
@@ -252,11 +250,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
@Override
public void onCreateSuccess(org.webrtc.SessionDescription description) {
final SessionDescription sessionDescription = SessionDescription.parse(description.description);
- Log.d(Config.LOGTAG,"description: "+description.description);
- for (SessionDescription.Media media : sessionDescription.media) {
- Log.d(Config.LOGTAG, RtpDescription.of(media).toString());
- }
- Log.d(Config.LOGTAG, sessionDescription.toString());
+ Log.d(Config.LOGTAG, "description: " + description.description);
+ final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
+ sendSessionInitiate(rtpContentMap);
}
@Override
@@ -306,44 +302,4 @@ public class JingleRtpConnection extends AbstractJingleConnection {
throw new IllegalStateException(String.format("Unable to transition from %s to %s", this.state, target));
}
}
-
- public static class DescriptionTransport {
- private final RtpDescription description;
- private final IceUdpTransportInfo transport;
-
- public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
- this.description = description;
- this.transport = transport;
- }
-
- public static DescriptionTransport of(final Content content) {
- final GenericDescription description = content.getDescription();
- final GenericTransportInfo transportInfo = content.getTransport();
- final RtpDescription rtpDescription;
- final IceUdpTransportInfo iceUdpTransportInfo;
- if (description instanceof RtpDescription) {
- rtpDescription = (RtpDescription) description;
- } else {
- Log.d(Config.LOGTAG, "description was " + description);
- throw new IllegalArgumentException("Content does not contain RtpDescription");
- }
- if (transportInfo instanceof IceUdpTransportInfo) {
- iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
- } else {
- throw new IllegalArgumentException("Content does not contain ICE-UDP transport");
- }
- return new DescriptionTransport(rtpDescription, iceUdpTransportInfo);
- }
-
- public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
- return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
- @NullableDecl
- @Override
- public DescriptionTransport apply(@NullableDecl Content content) {
- return content == null ? null : of(content);
- }
- }));
- }
- }
-
}
@@ -0,0 +1,108 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import android.util.Log;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+import java.util.Map;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
+import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
+import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
+import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
+
+public class RtpContentMap {
+
+ public final Group group;
+ public final Map<String, DescriptionTransport> contents;
+
+ private RtpContentMap(Group group, Map<String, DescriptionTransport> contents) {
+ this.group = group;
+ this.contents = contents;
+ }
+
+ public static RtpContentMap of(final JinglePacket jinglePacket) {
+ return new RtpContentMap(jinglePacket.getGroup(), DescriptionTransport.of(jinglePacket.getJingleContents()));
+ }
+
+ public static RtpContentMap of(final SessionDescription sessionDescription) {
+ final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
+ for (SessionDescription.Media media : sessionDescription.media) {
+ final String id = Iterables.getFirst(media.attributes.get("mid"), null);
+ Preconditions.checkNotNull(id, "media has no mid");
+ contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
+ }
+ final String groupAttribute = Iterables.getFirst(sessionDescription.attributes.get("group"), null);
+ final Group group = groupAttribute == null ? null : Group.ofSdpString(groupAttribute);
+ return new RtpContentMap(group, contentMapBuilder.build());
+ }
+
+ public JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) {
+ final JinglePacket jinglePacket = new JinglePacket(action, sessionId);
+ if (this.group != null) {
+ jinglePacket.addGroup(this.group);
+ }
+ for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
+ final Content content = new Content(Content.Creator.INITIATOR, entry.getKey());
+ content.addChild(entry.getValue().description);
+ content.addChild(entry.getValue().transport);
+ jinglePacket.addJingleContent(content);
+ }
+ return jinglePacket;
+ }
+
+ public static class DescriptionTransport {
+ public final RtpDescription description;
+ public final IceUdpTransportInfo transport;
+
+ public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
+ this.description = description;
+ this.transport = transport;
+ }
+
+ public static DescriptionTransport of(final Content content) {
+ final GenericDescription description = content.getDescription();
+ final GenericTransportInfo transportInfo = content.getTransport();
+ final RtpDescription rtpDescription;
+ final IceUdpTransportInfo iceUdpTransportInfo;
+ if (description instanceof RtpDescription) {
+ rtpDescription = (RtpDescription) description;
+ } else {
+ Log.d(Config.LOGTAG, "description was " + description);
+ throw new IllegalArgumentException("Content does not contain RtpDescription");
+ }
+ if (transportInfo instanceof IceUdpTransportInfo) {
+ iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
+ } else {
+ throw new IllegalArgumentException("Content does not contain ICE-UDP transport");
+ }
+ return new DescriptionTransport(rtpDescription, iceUdpTransportInfo);
+ }
+
+ public static DescriptionTransport of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
+ final RtpDescription rtpDescription = RtpDescription.of(media);
+ final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
+ return new DescriptionTransport(rtpDescription, transportInfo);
+ }
+
+ public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
+ return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
+ @NullableDecl
+ @Override
+ public DescriptionTransport apply(@NullableDecl Content content) {
+ return content == null ? null : of(content);
+ }
+ }));
+ }
+ }
+}
@@ -28,7 +28,7 @@ public class SessionDescription {
this.media = media;
}
- public static SessionDescription parse(final Map<String, JingleRtpConnection.DescriptionTransport> contents) {
+ public static SessionDescription parse(final Map<String, RtpContentMap.DescriptionTransport> contents) {
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
return sessionDescriptionBuilder.createSessionDescription();
}
@@ -0,0 +1,64 @@
+package eu.siacs.conversations.xmpp.jingle.stanzas;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.Namespace;
+
+public class Group extends Element {
+
+ private Group() {
+ super("group", Namespace.JINGLE_APPS_GROUPING);
+ }
+
+ public Group(final String semantics, final Collection<String> identificationTags) {
+ super("group", Namespace.JINGLE_APPS_GROUPING);
+ this.setAttribute("semantics", semantics);
+ for (String tag : identificationTags) {
+ this.addChild(new Element("content").setAttribute("name", tag));
+ }
+ }
+
+ public String getSemantics() {
+ return this.getAttribute("semantics");
+ }
+
+ public List<String> getIdentificationTags() {
+ final ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
+ for (final Element child : this.children) {
+ if ("content".equals(child.getName())) {
+ final String name = child.getAttribute("name");
+ if (name != null) {
+ builder.add(name);
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ public static Group ofSdpString(final String input) {
+ ImmutableList.Builder<String> tagBuilder = new ImmutableList.Builder<>();
+ final String[] parts = input.split(" ");
+ if (parts.length >= 2) {
+ final String semantics = parts[0];
+ for(int i = 1; i < parts.length; ++i) {
+ tagBuilder.add(parts[i]);
+ }
+ return new Group(semantics,tagBuilder.build());
+ }
+ return null;
+ }
+
+ public static Group upgrade(final Element element) {
+ Preconditions.checkArgument("group".equals(element.getName()));
+ Preconditions.checkArgument(Namespace.JINGLE_APPS_GROUPING.equals(element.getNamespace()));
+ final Group group = new Group();
+ group.setAttributes(element.getAttributes());
+ group.setChildren(element.getChildren());
+ return group;
+ }
+}
@@ -10,8 +10,8 @@ import eu.siacs.conversations.xml.Namespace;
public class IceUdpTransportInfo extends GenericTransportInfo {
- private IceUdpTransportInfo(final String name, final String xmlns) {
- super(name, xmlns);
+ public IceUdpTransportInfo() {
+ super("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
}
public Fingerprint getFingerprint() {
@@ -32,7 +32,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
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");
- final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo("transport", Namespace.JINGLE_TRANSPORT_ICE_UDP);
+ final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
transportInfo.setAttributes(element.getAttributes());
transportInfo.setChildren(element.getChildren());
return transportInfo;
@@ -1,7 +1,6 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
import android.support.annotation.NonNull;
-import android.util.Log;
import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
@@ -9,7 +8,6 @@ import com.google.common.collect.ImmutableMap;
import java.util.Map;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -44,6 +42,15 @@ public class JinglePacket extends IqPacket {
return content == null ? null : Content.upgrade(content);
}
+ public Group getGroup() {
+ final Element group = this.findChild("group", Namespace.JINGLE_APPS_GROUPING);
+ return group == null ? null : Group.upgrade(group);
+ }
+
+ public void addGroup(final Group group) {
+ this.addJingleChild(group);
+ }
+
public Map<String, Content> getJingleContents() {
final Element jingle = findChild("jingle", Namespace.JINGLE);
ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
@@ -56,8 +63,8 @@ public class JinglePacket extends IqPacket {
return builder.build();
}
- public void setJingleContent(final Content content) { //take content interface
- setJingleChild(content);
+ public void addJingleContent(final Content content) { //take content interface
+ addJingleChild(content);
}
public Reason getReason() {
@@ -87,7 +94,7 @@ public class JinglePacket extends IqPacket {
return jingle == null ? null : jingle.findChild(name);
}
- public void setJingleChild(final Element child) {
+ public void addJingleChild(final Element child) {
final Element jingle = findChild("jingle", Namespace.JINGLE);
jingle.addChild(child);
}
@@ -19,6 +19,11 @@ import eu.siacs.conversations.xmpp.jingle.SessionDescription;
public class RtpDescription extends GenericDescription {
+ private RtpDescription(final String media) {
+ super("description", Namespace.JINGLE_APPS_RTP);
+ this.setAttribute("media", media);
+ }
+
private RtpDescription() {
super("description", Namespace.JINGLE_APPS_RTP);
}
@@ -447,7 +452,7 @@ public class RtpDescription extends GenericDescription {
}
public static RtpDescription of(final SessionDescription.Media media) {
- final RtpDescription rtpDescription = new RtpDescription();
+ final RtpDescription rtpDescription = new RtpDescription(media.media);
final Map<String, List<Parameter>> parameterMap = new HashMap<>();
final ArrayListMultimap<String, Element> feedbackNegotiationMap = ArrayListMultimap.create();
final ArrayListMultimap<String, Source.Parameter> sourceParameterMap = ArrayListMultimap.create();