require dtls and ensure procceds get tracked

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/generator/MessageGenerator.java           |  2 
src/main/java/eu/siacs/conversations/parser/MessageParser.java                 | 13 
src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java |  3 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java  |  8 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java      | 22 
src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java            | 22 
6 files changed, 55 insertions(+), 15 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/generator/MessageGenerator.java 🔗

@@ -238,7 +238,7 @@ public class MessageGenerator extends AbstractGenerator {
 		final MessagePacket packet = new MessagePacket();
         packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
 		packet.setTo(proposal.with);
-		packet.setId(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX+proposal.sessionId);
+		packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX +proposal.sessionId);
 		final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
 		propose.setAttribute("id", proposal.sessionId);
 		propose.addChild("description", Namespace.JINGLE_APPS_RTP);

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -306,12 +306,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             final Jid from = packet.getFrom();
             final String id = packet.getId();
             if (from != null && id != null) {
-                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX.length());
+                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
+                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
                     mXmppConnectionService.getJingleConnectionManager()
                             .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
                     return true;
                 }
+                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
+                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
+                    mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId);
+                    return true;
+                }
                 mXmppConnectionService.markMessage(account,
                         from.asBareJid(),
                         id,
@@ -845,8 +850,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                     query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
                 }
             } else if (id != null) {
-                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX.length());
+                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
+                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
                     mXmppConnectionService.getJingleConnectionManager()
                             .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
                 } else {

src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java 🔗

@@ -11,7 +11,8 @@ import rocks.xmpp.addr.Jid;
 
 public abstract class AbstractJingleConnection {
 
-    public static final String JINGLE_MESSAGE_ID_PREFIX = "jm-propose-";
+    public static final String JINGLE_MESSAGE_PROPOSE_ID_PREFIX = "jm-propose-";
+    public static final String JINGLE_MESSAGE_PROCEED_ID_PREFIX = "jm-proceed-";
 
     protected final JingleConnectionManager jingleConnectionManager;
     protected final XmppConnectionService xmppConnectionService;

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -354,6 +354,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
     }
 
+    public void failProceed(Account account, final Jid with, String sessionId) {
+        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
+        final AbstractJingleConnection existingJingleConnection = connections.get(id);
+        if (existingJingleConnection instanceof JingleRtpConnection) {
+            ((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
+        }
+    }
+
     public static class RtpSessionProposal {
         private final Account account;
         public final Jid with;

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -81,7 +81,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     private RtpContentMap responderRtpContentMap;
 
 
-    public JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
+    JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
         super(jingleConnectionManager, id, initiator);
     }
 
@@ -186,8 +186,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         try {
             contentMap = RtpContentMap.of(jinglePacket);
             contentMap.requireContentDescriptions();
-            //TODO requireTransportWithDtls();
-        } catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
+            contentMap.requireDTLSFingerprint();
+        } catch (final IllegalArgumentException | IllegalStateException | NullPointerException e) {
             respondOk(jinglePacket);
             sendSessionTerminate(Reason.FAILED_APPLICATION);
             Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
@@ -226,7 +226,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         try {
             contentMap = RtpContentMap.of(jinglePacket);
             contentMap.requireContentDescriptions();
-            //TODO requireTransportWithDtls();
+            contentMap.requireDTLSFingerprint();
         } catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
             respondOk(jinglePacket);
             Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", e);
@@ -351,6 +351,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         }
     }
 
+    void deliverFailedProceed() {
+        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": receive message error for proceed message");
+        if (transition(State.TERMINATED_CONNECTIVITY_ERROR)) {
+            webRTCWrapper.close();
+            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into connectivity error");
+            this.jingleConnectionManager.finishConnection(this);
+        }
+    }
+
     private void receiveAccept(Jid from, Element message) {
         final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
         if (originatedFromMyself) {
@@ -533,7 +542,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     }
 
     private void terminateWithOutOfOrder(final JinglePacket jinglePacket) {
-        Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": terminating session with out-of-order");
+        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": terminating session with out-of-order");
         webRTCWrapper.close();
         transitionOrThrow(State.TERMINATED_APPLICATION_FAILURE);
         respondWithOutOfOrder(jinglePacket);
@@ -681,6 +690,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
 
     private void sendJingleMessage(final String action, final Jid to) {
         final MessagePacket messagePacket = new MessagePacket();
+        if ("proceed".equals(action)) {
+            messagePacket.setId(JINGLE_MESSAGE_PROCEED_ID_PREFIX + id.sessionId);
+        }
         messagePacket.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
         messagePacket.setTo(to);
         messagePacket.addChild(action, Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId);

src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java 🔗

@@ -4,6 +4,7 @@ import android.util.Log;
 
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
@@ -51,13 +52,26 @@ public class RtpContentMap {
         if (this.contents.size() == 0) {
             throw new IllegalStateException("No contents available");
         }
-        for(Map.Entry<String,DescriptionTransport> entry : this.contents.entrySet()) {
+        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
             if (entry.getValue().description == null) {
                 throw new IllegalStateException(String.format("%s is lacking content description", entry.getKey()));
             }
         }
     }
 
+    public void requireDTLSFingerprint() {
+        if (this.contents.size() == 0) {
+            throw new IllegalStateException("No contents available");
+        }
+        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
+            final IceUdpTransportInfo transport = entry.getValue().transport;
+            final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
+            if (fingerprint == null || Strings.isNullOrEmpty(fingerprint.getContent()) || Strings.isNullOrEmpty(fingerprint.getHash())) {
+                throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s", entry.getKey()));
+            }
+        }
+    }
+
     public JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) {
         final JinglePacket jinglePacket = new JinglePacket(action, sessionId);
         if (this.group != null) {
@@ -75,14 +89,14 @@ public class RtpContentMap {
     }
 
     public RtpContentMap transportInfo(final String contentName, final IceUdpTransportInfo.Candidate candidate) {
-        final RtpContentMap.DescriptionTransport descriptionTransport =  contents.get(contentName);
+        final RtpContentMap.DescriptionTransport descriptionTransport = contents.get(contentName);
         final IceUdpTransportInfo transportInfo = descriptionTransport == null ? null : descriptionTransport.transport;
         if (transportInfo == null) {
-            throw new IllegalArgumentException("Unable to find transport info for content name "+contentName);
+            throw new IllegalArgumentException("Unable to find transport info for content name " + contentName);
         }
         final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper();
         newTransportInfo.addChild(candidate);
-        return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null,newTransportInfo)));
+        return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo)));
 
     }