diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 20fbae868..ae951ddfe 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -81,7 +81,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { final AbstractJingleConnection existingJingleConnection = connections.get(id); if (existingJingleConnection != null) { if (existingJingleConnection instanceof JingleRtpConnection) { - ((JingleRtpConnection) existingJingleConnection).deliveryMessage(to, from, message); + ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message); } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages"); } @@ -91,7 +91,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (Namespace.JINGLE_APPS_RTP.equals(namespace)) { final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, with); this.connections.put(id, rtpConnection); - rtpConnection.deliveryMessage(to, from, message); + rtpConnection.deliveryMessage(from, message); } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed " + namespace + " session"); } 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 1d30e22bc..d178fd2f5 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -2,8 +2,12 @@ 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 java.util.Collection; import java.util.Map; @@ -11,7 +15,12 @@ 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; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import rocks.xmpp.addr.Jid; @@ -23,6 +32,7 @@ public class JingleRtpConnection extends AbstractJingleConnection { final ImmutableMap.Builder> transitionBuilder = new ImmutableMap.Builder<>(); transitionBuilder.put(State.NULL, ImmutableList.of(State.PROPOSED, State.SESSION_INITIALIZED)); transitionBuilder.put(State.PROPOSED, ImmutableList.of(State.ACCEPTED, State.PROCEED)); + transitionBuilder.put(State.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED)); VALID_TRANSITIONS = transitionBuilder.build(); } @@ -36,28 +46,91 @@ public class JingleRtpConnection extends AbstractJingleConnection { @Override void deliverPacket(final JinglePacket jinglePacket) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": packet delivered to JingleRtpConnection"); - Log.d(Config.LOGTAG, jinglePacket.toString()); + switch (jinglePacket.getAction()) { + case SESSION_INITIATE: + receiveSessionInitiate(jinglePacket); + break; + default: + Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction())); + break; + } } - void deliveryMessage(final Jid to, final Jid from, final Element message) { + private void receiveSessionInitiate(final JinglePacket jinglePacket) { + if (isInitiator()) { + Log.d(Config.LOGTAG, String.format("%s: received session-initiate even though we were initiating", id.account.getJid().asBareJid())); + //TODO respond with out-of-order + return; + } + final Map contents; + try { + contents = DescriptionTransport.of(jinglePacket.getJingleContents()); + } 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"); + final State oldState = this.state; + if (transition(State.SESSION_INITIALIZED)) { + if (oldState == State.PROCEED) { + sendSessionAccept(); + } else { + //TODO start ringing + } + } else { + Log.d(Config.LOGTAG, String.format("%s: received session-initiate while in state %s", id.account.getJid().asBareJid(), state)); + } + } + + void deliveryMessage(final Jid from, final Element message) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": delivered message to JingleRtpConnection " + message); - final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid()); switch (message.getName()) { case "propose": - if (originatedFromMyself) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring"); - } else if (transition(State.PROPOSED)) { - //TODO start ringing or something - pickUpCall(); - } else { - Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring session proposal because already in " + state); - } + receivePropose(from, message); break; + case "proceed": + receiveProceed(from, message); default: break; } } + private void receivePropose(final Jid from, final Element propose) { + final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid()); + if (originatedFromMyself) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring"); + } else if (transition(State.PROPOSED)) { + //TODO start ringing or something + pickUpCall(); + } else { + Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring session proposal because already in " + state); + } + } + + private void receiveProceed(final Jid from, final Element proceed) { + if (from.equals(id.with)) { + if (isInitiator()) { + if (transition(State.SESSION_INITIALIZED)) { + this.sendSessionInitiate(); + } else { + Log.d(Config.LOGTAG, String.format("%s: ignoring proceed because already in %s", id.account.getJid().asBareJid(), this.state)); + } + } else { + Log.d(Config.LOGTAG, String.format("%s: ignoring proceed because we were not initializing", id.account.getJid().asBareJid())); + } + } else { + Log.d(Config.LOGTAG, String.format("%s: ignoring proceed from %s. was expected from %s", id.account.getJid().asBareJid(), from, id.with)); + } + } + + private void sendSessionInitiate() { + + } + + private void sendSessionAccept() { + Log.d(Config.LOGTAG,"sending session-accept"); + } + public void pickUpCall() { switch (this.state) { case PROPOSED: @@ -75,8 +148,8 @@ public class JingleRtpConnection extends AbstractJingleConnection { transitionOrThrow(State.PROCEED); final MessagePacket messagePacket = new MessagePacket(); messagePacket.setTo(id.with); - //Note that Movim needs 'accept' - messagePacket.addChild("proceed", Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId); + //Note that Movim needs 'accept', correct is 'proceed' https://github.com/movim/movim/issues/916 + messagePacket.addChild("accept", Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId); Log.d(Config.LOGTAG, messagePacket.toString()); xmppConnectionService.sendMessagePacket(id.account, messagePacket); } @@ -102,4 +175,42 @@ public class JingleRtpConnection extends AbstractJingleConnection { } } + private 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 { + 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 of(final Map contents) { + return Maps.transformValues(contents, new Function() { + @NullableDecl + @Override + public DescriptionTransport apply(@NullableDecl Content content) { + return content == null ? null : of(content); + } + }); + } + } + } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index ad16041a3..a815155dd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -30,6 +30,10 @@ public class Content extends Element { return content; } + public String getContentName() { + return this.getAttribute("name"); + } + public Creator getCreator() { return Creator.of(getAttribute("creator")); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java index b90333126..a76c841bf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java @@ -1,10 +1,15 @@ 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; +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; @@ -39,6 +44,18 @@ public class JinglePacket extends IqPacket { return content == null ? null : Content.upgrade(content); } + public Map getJingleContents() { + final Element jingle = findChild("jingle", Namespace.JINGLE); + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (final Element child : jingle.getChildren()) { + if ("content".equals(child.getName())) { + final Content content = Content.upgrade(child); + builder.put(content.getContentName(), content); + } + } + return builder.build(); + } + public void setJingleContent(final Content content) { //take content interface setJingleChild(content); }