parse sdp to jingle (yet w/o transport)

This commit is contained in:
Daniel Gultsch 2020-04-05 10:20:34 +02:00
parent ef51ec2c1d
commit b44a3aeac6
9 changed files with 222 additions and 81 deletions

View File

@ -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";

View File

@ -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) {

View File

@ -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);
}
}));
}
}
}

View File

@ -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);
}
}));
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();