Detailed changes
@@ -38,6 +38,7 @@ public final class Namespace {
public static final String JINGLE_APPS_DTLS = "urn:xmpp:jingle:apps:dtls: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";
public static final String IBB = "http://jabber.org/protocol/ibb";
public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0";
@@ -73,6 +73,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
final State oldState = this.state;
if (transition(State.SESSION_INITIALIZED)) {
if (oldState == State.PROCEED) {
+ processContents(contents);
sendSessionAccept();
} else {
//TODO start ringing
@@ -82,6 +83,23 @@ 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();
+ 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()) {
+ Log.d(Config.LOGTAG,"payload type: "+payloadType.toString());
+ }
+ for(RtpDescription.RtpHeaderExtension extension : rtpDescription.getHeaderExtensions()) {
+ Log.d(Config.LOGTAG,"extension: "+extension.toString());
+ }
+ final IceUdpTransportInfo iceUdpTransportInfo = descriptionTransport.transport;
+ Log.d(Config.LOGTAG,"transport: "+descriptionTransport.transport);
+ Log.d(Config.LOGTAG,"fingerprint "+iceUdpTransportInfo.getFingerprint());
+ }
+ }
+
void deliveryMessage(final Jid from, final Element message) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": delivered message to JingleRtpConnection " + message);
switch (message.getName()) {
@@ -175,7 +193,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
}
}
- private static class DescriptionTransport {
+ public static class DescriptionTransport {
private final RtpDescription description;
private final IceUdpTransportInfo transport;
@@ -192,6 +210,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
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) {
@@ -203,13 +222,13 @@ public class JingleRtpConnection extends AbstractJingleConnection {
}
public static Map<String, DescriptionTransport> of(final Map<String,Content> contents) {
- return Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
+ 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,11 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.util.Map;
+
+public class SdpUtils {
+
+ public static String toSdpString(Map<String, JingleRtpConnection.DescriptionTransport> contents) {
+ return "";
+ }
+
+}
@@ -51,9 +51,11 @@ public class Content extends Element {
if (description == null) {
return null;
}
- final String xmlns = description.getNamespace();
- if (FileTransferDescription.NAMESPACES.contains(xmlns)) {
+ final String namespace = description.getNamespace();
+ if (FileTransferDescription.NAMESPACES.contains(namespace)) {
return FileTransferDescription.upgrade(description);
+ } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
+ return RtpDescription.upgrade(description);
} else {
return GenericDescription.upgrade(description);
}
@@ -1,6 +1,9 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
@@ -11,6 +14,21 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
super(name, xmlns);
}
+ public Fingerprint getFingerprint() {
+ final Element fingerprint = this.findChild("fingerprint", Namespace.JINGLE_APPS_DTLS);
+ return fingerprint == null ? null : Fingerprint.upgrade(fingerprint);
+ }
+
+ public List<Candidate> getCandidates() {
+ final ImmutableList.Builder<Candidate> builder = new ImmutableList.Builder<>();
+ for(final Element child : getChildren()) {
+ if ("candidate".equals(child.getName())) {
+ builder.add(Candidate.upgrade(child));
+ }
+ }
+ return builder.build();
+ }
+
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");
@@ -19,4 +37,104 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
transportInfo.setChildren(element.getChildren());
return transportInfo;
}
+
+ public static class Candidate extends Element {
+
+ private Candidate() {
+ super("candidate");
+ }
+
+ public int getComponent() {
+ return getAttributeAsInt("component");
+ }
+
+ public int getFoundation() {
+ return getAttributeAsInt("foundation");
+ }
+
+ public int getGeneration() {
+ return getAttributeAsInt("generation");
+ }
+
+ public String getId() {
+ return getAttribute("id");
+ }
+
+ public String getIp() {
+ return getAttribute("ip");
+ }
+
+ public int getNetwork() {
+ return getAttributeAsInt("network");
+ }
+
+ public int getPort() {
+ return getAttributeAsInt("port");
+ }
+
+ public int getPriority() {
+ return getAttributeAsInt("priority");
+ }
+
+ public String getProtocol() {
+ return getAttribute("protocol");
+ }
+
+ public String getRelAddr() {
+ return getAttribute("rel-addr");
+ }
+
+ public int getRelPort() {
+ return getAttributeAsInt("rel-port");
+ }
+
+ public String getType() { //TODO might be converted to enum
+ return getAttribute("type");
+ }
+
+ private int getAttributeAsInt(final String name) {
+ final String value = this.getAttribute(name);
+ if (value == null) {
+ return 0;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ public static Candidate upgrade(final Element element) {
+ Preconditions.checkArgument("candidate".equals(element.getName()));
+ final Candidate candidate = new Candidate();
+ candidate.setAttributes(element.getAttributes());
+ candidate.setChildren(element.getChildren());
+ return candidate;
+ }
+ }
+
+
+ public static class Fingerprint extends Element {
+
+ public String getHash() {
+ return this.getAttribute("hash");
+ }
+
+ public String getSetup() {
+ return this.getAttribute("setup");
+ }
+
+ private Fingerprint() {
+ super("fingerprint", Namespace.JINGLE_APPS_DTLS);
+ }
+
+ public static Fingerprint upgrade(final Element element) {
+ Preconditions.checkArgument("fingerprint".equals(element.getName()));
+ Preconditions.checkArgument(Namespace.JINGLE_APPS_DTLS.equals(element.getNamespace()));
+ final Fingerprint fingerprint = new Fingerprint();
+ fingerprint.setAttributes(element.getAttributes());
+ fingerprint.setContent(element.getContent());
+ return fingerprint;
+ }
+ }
}
@@ -38,7 +38,7 @@ public class JinglePacket extends IqPacket {
return jinglePacket;
}
- //TODO can have multiple contents
+ //TODO deprecate this somehow and make file transfer fail if there are multiple (or something)
public Content getJingleContent() {
final Element content = getJingleChild("content");
return content == null ? null : Content.upgrade(content);
@@ -1,6 +1,10 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Locale;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
@@ -12,6 +16,30 @@ public class RtpDescription extends GenericDescription {
super(name, namespace);
}
+ public Media getMedia() {
+ return Media.of(this.getAttribute("media"));
+ }
+
+ public List<PayloadType> getPayloadTypes() {
+ final ImmutableList.Builder<PayloadType> builder = new ImmutableList.Builder<>();
+ for(Element child : getChildren()) {
+ if ("payload-type".equals(child.getName())) {
+ builder.add(PayloadType.of(child));
+ }
+ }
+ return builder.build();
+ }
+
+ public List<RtpHeaderExtension> getHeaderExtensions() {
+ final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>();
+ for(final Element child : getChildren()) {
+ if ("rtp-hdrext".equals(child.getName()) && Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(child.getNamespace())) {
+ builder.add(RtpHeaderExtension.upgrade(child));
+ }
+ }
+ return builder.build();
+ }
+
public static RtpDescription upgrade(final Element element) {
Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description");
Preconditions.checkArgument(Namespace.JINGLE_APPS_RTP.equals(element.getNamespace()), "Element does not match the jingle rtp namespace");
@@ -20,4 +48,133 @@ public class RtpDescription extends GenericDescription {
description.setChildren(element.getChildren());
return description;
}
+
+ //TODO: support for https://xmpp.org/extensions/xep-0293.html
+
+
+ public static class RtpHeaderExtension extends Element {
+
+ private RtpHeaderExtension() {
+ super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public String getUri() {
+ return this.getAttribute("uri");
+ }
+
+ public static RtpHeaderExtension upgrade(final Element element) {
+ Preconditions.checkArgument("rtp-hdrext".equals(element.getName()));
+ Preconditions.checkArgument(Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(element.getNamespace()));
+ final RtpHeaderExtension extension = new RtpHeaderExtension();
+ extension.setAttributes(element.getAttributes());
+ extension.setChildren(element.getChildren());
+ return extension;
+ }
+ }
+
+ public static class PayloadType extends Element {
+
+ private PayloadType(String name, String xmlns) {
+ super(name, xmlns);
+ }
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public String getPayloadTypeName() {
+ return this.getAttribute("name");
+ }
+
+ public int getClockRate() {
+ final String clockRate = this.getAttribute("clockrate");
+ if (clockRate == null) {
+ return 0;
+ }
+ try {
+ return Integer.parseInt(clockRate);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ public int getChannels() {
+ final String channels = this.getAttribute("channels");
+ if (channels == null) {
+ return 1; // The number of channels; if omitted, it MUST be assumed to contain one channel
+ }
+ try {
+ return Integer.parseInt(channels);
+ } catch (NumberFormatException e) {
+ return 1;
+ }
+ }
+
+ public List<Parameter> getParameters() {
+ final ImmutableList.Builder<Parameter> builder = new ImmutableList.Builder<>();
+ for (Element child : getChildren()) {
+ if ("parameter".equals(child.getName())) {
+ builder.add(Parameter.of(child));
+ }
+ }
+ return builder.build();
+ }
+
+ public static PayloadType of(final Element element) {
+ Preconditions.checkArgument("payload-type".equals(element.getName()), "element name must be called payload-type");
+ PayloadType payloadType = new PayloadType("payload-type", Namespace.JINGLE_APPS_RTP);
+ payloadType.setAttributes(element.getAttributes());
+ payloadType.setChildren(element.getChildren());
+ return payloadType;
+ }
+ }
+
+ public static class Parameter extends Element {
+
+ private Parameter() {
+ super("parameter", Namespace.JINGLE_APPS_RTP);
+ }
+
+ public Parameter(String name, String value) {
+ super("parameter", Namespace.JINGLE_APPS_RTP);
+ this.setAttribute("name", name);
+ this.setAttribute("value", value);
+ }
+
+ public String getParameterName() {
+ return this.getAttribute("name");
+ }
+
+ public String getParameterValue() {
+ return this.getAttribute("value");
+ }
+
+ public static Parameter of(final Element element) {
+ Preconditions.checkArgument("parameter".equals(element.getName()), "element name must be called parameter");
+ Parameter parameter = new Parameter();
+ parameter.setAttributes(element.getAttributes());
+ parameter.setChildren(element.getChildren());
+ return parameter;
+ }
+ }
+
+ public enum Media {
+ VIDEO, AUDIO, UNKNOWN;
+
+ @Override
+ public String toString() {
+ return super.toString().toLowerCase(Locale.ROOT);
+ }
+
+ public static Media of(String value) {
+ try {
+ return value == null ? UNKNOWN : Media.valueOf(value.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ return UNKNOWN;
+ }
+ }
+ }
}