diff --git a/.classpath b/.classpath
index 2673ac531ee3985a0325cb58d77f48b9d4ed61a9..eeb6f8233a9044fff9c3f1c436c46c8e46d9d524 100644
--- a/.classpath
+++ b/.classpath
@@ -3,7 +3,8 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry kind="lib" path="lib/bccontrib-1.0-SNAPSHOT.jar"/>
-	<classpathentry kind="lib" path="lib/bcprov-jdk15on-148.jar"/>
+	<classpathentry kind="lib" path="lib/bcpkix-jdk15on-149.jar" sourcepath="C:/Users/lyubomir/Documents/bcpkix-jdk15on-149/src.zip"/>
+	<classpathentry kind="lib" path="lib/bcprov-jdk15on-149.jar" sourcepath="C:/Users/lyubomir/Documents/bcprov-jdk15on-149/src.zip"/>
 	<classpathentry kind="lib" path="lib/fmj.jar"/>
 	<classpathentry kind="lib" path="lib/ice4j.jar"/>
 	<classpathentry kind="lib" path="lib/jain-sdp.jar"/>
diff --git a/lib/bcpkix-jdk15on-149.jar b/lib/bcpkix-jdk15on-149.jar
new file mode 100644
index 0000000000000000000000000000000000000000..96d1985565c9a0c7a470ba4ca0d9e4e3cdf7405c
Binary files /dev/null and b/lib/bcpkix-jdk15on-149.jar differ
diff --git a/lib/bcprov-jdk15on-148.jar b/lib/bcprov-jdk15on-149.jar
similarity index 55%
rename from lib/bcprov-jdk15on-148.jar
rename to lib/bcprov-jdk15on-149.jar
index 3fcb136da27867a203f26f607567b973062f6ca9..e1d4bb31ce19fd4608d2c8489891daddbbeebc90 100644
Binary files a/lib/bcprov-jdk15on-148.jar and b/lib/bcprov-jdk15on-149.jar differ
diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java
index ea73980ccc06d0033bc8c170e2cc8fc84623620e..20caa1ea3850e23cf246ef7781cb0df67bf5d400 100755
--- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java
+++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java
@@ -23,7 +23,9 @@
 import org.jitsi.impl.neomedia.codec.video.*;
 import org.jitsi.impl.neomedia.device.*;
 import org.jitsi.impl.neomedia.format.*;
+import org.jitsi.impl.neomedia.transform.dtls.*;
 import org.jitsi.impl.neomedia.transform.sdes.*;
+import org.jitsi.impl.neomedia.transform.zrtp.*;
 import org.jitsi.service.configuration.*;
 import org.jitsi.service.libjitsi.*;
 import org.jitsi.service.neomedia.*;
@@ -704,27 +706,21 @@ private MediaDevice getNonSendVideoDevice()
     }
 
     /**
-     * Initializes a new <tt>ZrtpControl</tt> instance which is to control all
-     * ZRTP options.
-     *
-     * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP
-     * options
-     */
-    public ZrtpControl createZrtpControl()
-    {
-        return new ZrtpControlImpl();
-    }
-
-    /**
-     * Initializes a new <tt>SDesControl</tt> instance which is to control all
-     * SDes options.
-     *
-     * @return a new <tt>SDesControl</tt> instance which is to control all SDes
-     * options
+     * {@inheritDoc}
      */
-    public SDesControl createSDesControl()
+    public SrtpControl createSrtpControl(SrtpControlType srtpControlType)
     {
-        return new SDesControlImpl();
+        switch (srtpControlType)
+        {
+        case DTLS_SRTP:
+            return new DtlsControlImpl();
+        case SDES:
+            return new SDesControlImpl();
+        case ZRTP:
+            return new ZrtpControlImpl();
+        default:
+            return null;
+        }
     }
 
     /**
diff --git a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
index 0b74b213cdb83034528db8ddc958b9eb9eaf1cc4..9d9c18941e3ea18f0a549cfd3b96e1f42b664c83 100644
--- a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
+++ b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
@@ -291,7 +291,7 @@ public MediaStreamImpl(
         this.srtpControl
                 = (srtpControl == null)
                     ? NeomediaServiceUtils.getMediaServiceImpl()
-                            .createZrtpControl()
+                            .createSrtpControl(SrtpControlType.ZRTP)
                     : srtpControl;
 
         if (connector != null)
diff --git a/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java b/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java
index 576a750d0340bcb27d940d8a5e6cb2e381d36405..b7089188028d9375b68f1c004736435c755afc26 100755
--- a/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java
+++ b/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java
@@ -35,7 +35,7 @@ public abstract class RTPConnectorInputStream
      * The length in bytes of the buffers of <tt>RTPConnectorInputStream</tt>
      * receiving packets from the network.
      */
-    private static final int PACKET_RECEIVE_BUFFER_LENGTH = 4 * 1024;
+    public static final int PACKET_RECEIVE_BUFFER_LENGTH = 4 * 1024;
 
     /**
      * Packet receive buffer
diff --git a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
index 63788bf7c7832d7087fd1b3edbbe60c1b7282aec..6afde13389e6bab94b09df36fdd83dd3f8a0978c 100644
--- a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
+++ b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
@@ -17,6 +17,8 @@
 import javax.media.rtp.event.*;
 import javax.media.rtp.rtcp.*;
 
+import net.sf.fmj.media.rtp.RTPHeader;
+
 import org.jitsi.impl.neomedia.protocol.*;
 import org.jitsi.service.neomedia.*;
 import org.jitsi.util.*;
@@ -763,7 +765,8 @@ private synchronized int read(
              * RTP packet?
              */
             if ((length >= 12)
-                    && (/* v */ ((buffer[offset] & 0xc0) >>> 6) == 2))
+                    && (/* v */ ((buffer[offset] & 0xc0) >>> 6)
+                            == RTPHeader.VERSION))
             {
                 long ssrc = readInt(buffer, offset + 8);
 
diff --git a/src/org/jitsi/impl/neomedia/RawPacket.java b/src/org/jitsi/impl/neomedia/RawPacket.java
index dc7ededd7f400532ca40c3d5e3cb056567109f5b..f2cea27faacff63525a4fee32aa84577e991b2ab 100644
--- a/src/org/jitsi/impl/neomedia/RawPacket.java
+++ b/src/org/jitsi/impl/neomedia/RawPacket.java
@@ -585,10 +585,10 @@ public byte[] readTimeStampIntoByteArray()
 
     /**
      * Returns <tt>true</tt> if the extension bit of this packet has been set
-     * and false otherwise.
+     * and <tt>false</tt> otherwise.
      *
      * @return  <tt>true</tt> if the extension bit of this packet has been set
-     * and false otherwise.
+     * and <tt>false</tt> otherwise.
      */
     public boolean getExtensionBit()
     {
diff --git a/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java
index 6d87bf798396c742161138c264930d49fbdb36f8..2e0de0506fd412f58db259818f5652bd976058e8 100755
--- a/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java
+++ b/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java
@@ -18,12 +18,10 @@
 public interface PacketTransformer
 {
     /**
-     * Transforms a specific packet.
-     *
-     * @param pkt the packet to be transformed
-     * @return the transformed packet
+     * Closes this <tt>PacketTransformer</tt> i.e. releases the resources
+     * allocated by it and prepares it for garbage collection.
      */
-    public RawPacket transform(RawPacket pkt);
+    public void close();
 
     /**
      * Reverse-transforms a specific packet (i.e. transforms a transformed
@@ -35,8 +33,10 @@ public interface PacketTransformer
     public RawPacket reverseTransform(RawPacket pkt);
 
     /**
-     * Closes this <tt>PacketTransformer</tt> i.e. releases the resources
-     * allocated by it and prepares it for garbage collection.
+     * Transforms a specific packet.
+     *
+     * @param pkt the packet to be transformed
+     * @return the transformed packet
      */
-    public void close();
+    public RawPacket transform(RawPacket pkt);
 }
diff --git a/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java b/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java
index 29da1a44c20e84d9932d295316c5a848123870f3..9bde2392461a282cfbb76d0648df9fb8a22a844e 100644
--- a/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java
+++ b/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java
@@ -148,8 +148,12 @@ public RawPacket transform(RawPacket pkt)
 
                 //the packet transformer may be null if for example the engine
                 //only does RTP transformations and this is an RTCP transformer.
-                if( pTransformer != null)
+                if (pTransformer != null)
+                {
                     pkt = pTransformer.transform(pkt);
+                    if (pkt == null)
+                        break;
+                }
             }
 
             return pkt;
@@ -174,11 +178,11 @@ public RawPacket reverseTransform(RawPacket pkt)
 
                 //the packet transformer may be null if for example the engine
                 //only does RTP transformations and this is an RTCP transformer.
-                if( pTransformer != null)
+                if (pTransformer != null)
                 {
                     pkt = pTransformer.reverseTransform(pkt);
                     if (pkt == null)
-                        return null;
+                        break;
                 }
             }
 
diff --git a/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java b/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java
index e3f3df9559f32fe5964b3a6a527ba76a9dae14f7..f2c9f2c91ba2b514268dc9d9229c546ddccc9ec7 100644
--- a/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java
+++ b/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java
@@ -67,12 +67,10 @@ protected RawPacket createRawPacket(byte[] buffer, int offset, int length)
             pkt = transformer.transform(pkt);
 
             /*
-             * This is for the case when the ZRTP engine stops the media stream
-             * allowing only ZRTP packets.
+             * XXX Allow transformer to abort the writing of buffer by not
+             * throwing a NullPointerException if pkt becomes null after
+             * transform.
              */
-            // TODO Comment in order to use the GoClear feature.
-            if ((pkt == null) && (targets.size() > 0))
-                throw new NullPointerException("pkt");
         }
         return pkt;
     }
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..77b2220f0b0f44ec1d328aa23d77a9b6e36a7cb8
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java
@@ -0,0 +1,341 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import java.io.*;
+import java.util.concurrent.*;
+
+import javax.media.rtp.*;
+
+import org.bouncycastle.crypto.tls.*;
+import org.ice4j.ice.*;
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.codec.video.h264.*;
+
+/**
+ * Implements {@link DatagramTransport} in order to integrate the Bouncy Castle
+ * Crypto APIs in libjitsi for the purposes of implementing DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class DatagramTransportImpl
+    implements DatagramTransport
+{
+    /**
+     * The ID of the component which this instance works for/is associated with.
+     */
+    private final int componentID;
+
+    /**
+     * The <tt>RTPConnector</tt> which represents and implements the actual
+     * <tt>DatagramSocket</tt> adapted by this instance.
+     */
+    private AbstractRTPConnector connector;
+
+    /**
+     * The queue of <tt>RawPacket</tt>s which have been received from the
+     * network are awaiting to be received by the application through this
+     * <tt>DatagramTransport</tt>.
+     */
+    private final ArrayBlockingQueue<MutableRawPacket> receiveQ;
+
+    /**
+     * The capacity of {@link #receiveQ}.
+     */
+    private final int receiveQCapacity;
+
+    /**
+     * Initializes a new <tt>DatagramTransportImpl</tt>.
+     *
+     * @param data {@link Component#RTP} if the new instance is to work on
+     * data/RTP packets or {@link Component#RTCP} if the new instance is to work
+     * on control/RTCP packets
+     */
+    public DatagramTransportImpl(int componentID)
+    {
+        switch (componentID)
+        {
+        case Component.RTCP:
+        case Component.RTP:
+            this.componentID = componentID;
+            break;
+        default:
+            throw new IllegalArgumentException("componentID");
+        }
+
+        receiveQCapacity
+            = RTPConnectorOutputStream
+                .MAX_PACKETS_PER_MILLIS_POLICY_PACKET_QUEUE_CAPACITY;
+        receiveQ = new ArrayBlockingQueue<MutableRawPacket>(receiveQCapacity);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close()
+        throws IOException
+    {
+        setConnector(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getReceiveLimit()
+        throws IOException
+    {
+        AbstractRTPConnector connector = this.connector;
+        int receiveLimit
+            = (connector == null) ? -1 : connector.getReceiveBufferSize();
+
+        if (receiveLimit <= 0)
+            receiveLimit = RTPConnectorInputStream.PACKET_RECEIVE_BUFFER_LENGTH;
+        return receiveLimit;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getSendLimit()
+        throws IOException
+    {
+        AbstractRTPConnector connector = this.connector;
+        int sendLimit
+            = (connector == null) ? -1 : connector.getSendBufferSize();
+
+        if (sendLimit <= 0)
+        {
+            /*
+             * XXX The estimation bellow is wildly inaccurate and hardly
+             * related... but we have to start somewhere.
+             */
+            sendLimit
+                = DtlsPacketTransformer.DTLS_RECORD_HEADER_LENGTH
+                    + Packetizer.MAX_PAYLOAD_SIZE;
+        }
+        return sendLimit;
+    }
+
+    /**
+     * Queues a packet received from the network to be received by the
+     * application through this <tt>DatagramTransport</tt>.
+     *
+     * @param buf the array of <tt>byte</tt>s which contains the packet to be
+     * queued
+     * @param off the offset within <tt>buf</tt> at which the packet to be
+     * queued starts
+     * @param len the length within <tt>buf</tt> starting at <tt>off</tt> of the
+     * packet to be queued
+     */
+    void queue(byte[] buf, int off, int len)
+    {
+        if (len > 0)
+        {
+            synchronized (receiveQ)
+            {
+                if (connector == null)
+                {
+                    throw new IllegalStateException(
+                            getClass().getName() + " is closed!");
+                }
+
+                byte[] pktBuf = new byte[len];
+                int pktOff = 0;
+
+                System.arraycopy(buf, off, pktBuf, pktOff, len);
+
+                MutableRawPacket pkt
+                    = new MutableRawPacket(pktBuf, pktOff, len);
+
+                if (receiveQ.size() == receiveQCapacity)
+                    receiveQ.remove();
+                receiveQ.add(pkt);
+                receiveQ.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        long enterTime = System.currentTimeMillis();
+        int received = 0;
+        boolean interrupted = false;
+
+        while (received < len)
+        {
+            long timeout;
+
+            if (waitMillis > 0)
+            {
+                timeout = waitMillis - System.currentTimeMillis() + enterTime;
+                if (timeout == 0 /* wait forever */)
+                    timeout = -1 /* do not wait */;
+            }
+            else
+            {
+                timeout = waitMillis;
+            }
+
+            synchronized (receiveQ)
+            {
+                if (connector == null)
+                    throw new IOException(getClass().getName() + " is closed!");
+
+                MutableRawPacket pkt = receiveQ.peek();
+
+                if (pkt != null)
+                {
+                    int toReceive = len - received;
+                    boolean toReceiveIsPositive = (toReceive > 0);
+
+                    if (toReceiveIsPositive)
+                    {
+                        int pktLength = pkt.getLength();
+                        int pktOffset = pkt.getOffset();
+
+                        if (toReceive > pktLength)
+                        {
+                            toReceive = pktLength;
+                            toReceiveIsPositive = (toReceive > 0);
+                        }
+                        if (toReceiveIsPositive)
+                        {
+                            System.arraycopy(
+                                    pkt.getBuffer(), pktOffset,
+                                    buf, off + received,
+                                    toReceive);
+                            received += toReceive;
+                        }
+                        if (toReceive == pktLength)
+                        {
+                            receiveQ.remove();
+                        }
+                        else
+                        {
+                            pkt.setLength(pktLength - toReceive);
+                            pkt.setOffset(pktOffset + toReceive);
+                        }
+                        if (toReceiveIsPositive)
+                        {
+                            /*
+                             * The specified buf has received toReceive bytes
+                             * and we do not concatenate RawPackets.
+                             */
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        // The specified buf has received at least len bytes.
+                        break;
+                    }
+                }
+
+                if (receiveQ.isEmpty() && (timeout >= 0))
+                {
+                    try
+                    {
+                        receiveQ.wait(timeout);
+                    }
+                    catch (InterruptedException ie)
+                    {
+                        interrupted = true;
+                    }
+                }
+            }
+        }
+        if (interrupted)
+            Thread.currentThread().interrupt();
+        return received;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        OutputDataStream outputStream;
+
+        switch (componentID)
+        {
+        case Component.RTCP:
+            outputStream = connector.getControlOutputStream();
+            break;
+        case Component.RTP:
+            outputStream = connector.getDataOutputStream();
+            break;
+        default:
+            throw new IllegalStateException("componentID");
+        }
+
+        outputStream.write(buf, off, len);
+    }
+
+    /**
+     * Sets the <tt>RTPConnector</tt> which represents and implements the actual
+     * <tt>DatagramSocket</tt> to be adapted by this instance.
+     * 
+     * @param connector the <tt>RTPConnector</tt> which represents and
+     * implements the actual <tt>DatagramSocket</tt> to be adapted by this
+     * instance
+     */
+    void setConnector(AbstractRTPConnector connector)
+    {
+        synchronized (receiveQ)
+        {
+            this.connector = connector;
+            receiveQ.notifyAll();
+        }
+    }
+
+    /**
+     * Exposes the <tt>setLength</tt> and <tt>setOffset</tt> methods of
+     * <tt>RawPacket</tt> to <tt>DatagramTransportImpl</tt>.
+     */
+    private static class MutableRawPacket
+        extends RawPacket
+    {
+        /**
+         * Initializes a new <tt>MutableRawPacket</tt> instance.
+         *
+         * @param buf the array of <tt>byte</tt>s to be represented by the new
+         * instance
+         * @param off the offset within <tt>buf</tt> at which the actual packet
+         * content to be represented by the new instance starts
+         * @param len the number of bytes within <tt>buf</tt> starting at
+         * <tt>offset</tt> which comprise the actual packet content to be
+         * represented by the new instance
+         */
+        public MutableRawPacket(byte[] buf, int off, int len)
+        {
+            super(buf, off, len);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void setLength(int length)
+        {
+            super.setLength(length);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void setOffset(int offset)
+        {
+            super.setOffset(offset);
+        }
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..85967ae66be872e09689afda3de92c883fcefa32
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java
@@ -0,0 +1,593 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import java.io.*;
+import java.math.*;
+import java.security.*;
+import java.util.*;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.x500.*;
+import org.bouncycastle.asn1.x500.style.*;
+import org.bouncycastle.asn1.x509.*;
+import org.bouncycastle.cert.*;
+import org.bouncycastle.crypto.*;
+import org.bouncycastle.crypto.generators.*;
+import org.bouncycastle.crypto.params.*;
+import org.bouncycastle.crypto.util.*;
+import org.bouncycastle.operator.*;
+import org.bouncycastle.operator.bc.*;
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.version.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements {@link DtlsControl} i.e. {@link SrtpControl} for DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class DtlsControlImpl
+    extends AbstractSrtpControl<DtlsTransformEngine>
+    implements DtlsControl
+{
+    /**
+     * The table which maps half-<tt>byte</tt>s to their hex characters.
+     */
+    private static final char[] HEX_ENCODE_TABLE
+        = {
+            '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+        };
+
+    /**
+     * The <tt>Logger</tt> used by the <tt>DtlsControlImpl</tt> class and its
+     * instances to print debug information.
+     */
+    private static final Logger logger
+        = Logger.getLogger(DtlsControlImpl.class);
+
+    /**
+     * The number of milliseconds within a day i.e. 24 hours.
+     */
+    private static final long ONE_DAY = 1000L * 60L * 60L * 24L;
+
+    /**
+     * The certificate with which the local endpoint represented by this
+     * instance authenticates its ends of DTLS sessions. 
+     */
+    private final org.bouncycastle.crypto.tls.Certificate certificate;
+
+    /**
+     * The <tt>RTPConnector</tt> which uses the <tt>TransformEngine</tt> of this
+     * <tt>SrtpControl</tt>.
+     */
+    private AbstractRTPConnector connector;
+
+    /**
+     * The indicator which determines whether this instance has been disposed
+     * i.e. prepared for garbage collection by {@link #cleanup()}.
+     */
+    private boolean disposed = false;
+
+    /**
+     * The DTLS protocol according to which this <tt>DtlsControl</tt> is to act
+     * either as a DTLS server or a DTLS client.
+     */
+    private int dtlsProtocol;
+
+    /**
+     * The private and public keys of {@link #certificate}.
+     */
+    private final AsymmetricCipherKeyPair keyPair;
+
+    /**
+     * The fingerprint of {@link #certificate}.
+     */
+    private final String localFingerprint;
+
+    /**
+     * The hash function of {@link #localFingerprint} (which is the same as the
+     * digest algorithm of the signature algorithm of {@link #certificate} in
+     * accord with RFC 4572).
+     */
+    private final String localFingerprintHashFunction;
+
+    /**
+     * The fingerprints presented by the remote endpoint via the signaling path. 
+     */
+    private Map<String,String> remoteFingerprints;
+
+    /**
+     * Initializes a new <tt>DtlsControlImpl</tt> instance.
+     */
+    public DtlsControlImpl()
+    {
+        super(SrtpControlType.DTLS_SRTP);
+
+        keyPair = generateKeyPair();
+
+        org.bouncycastle.asn1.x509.Certificate x509Certificate
+            = generateX509Certificate(generateCN(), keyPair);
+
+        certificate
+            = new org.bouncycastle.crypto.tls.Certificate(
+                    new org.bouncycastle.asn1.x509.Certificate[]
+                            {
+                                x509Certificate
+                            });
+        localFingerprintHashFunction = findHashFunction(x509Certificate);
+        localFingerprint
+            = computeFingerprint(
+                    x509Certificate,
+                    localFingerprintHashFunction);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void cleanup()
+    {
+        super.cleanup();
+
+        setConnector(null);
+
+        synchronized (this)
+        {
+            disposed = true;
+            notifyAll();
+        }
+    }
+
+    /**
+     * Computes the fingerprint of a specific certificate using a specific
+     * hash function.
+     *
+     * @param certificate the certificate the fingerprint of which is to be
+     * computed
+     * @param hashFunction the hash function to be used in order to compute the
+     * fingerprint of the specified <tt>certificate</tt> 
+     * @return the fingerprint of the specified <tt>certificate</tt> computed
+     * using the specified <tt>hashFunction</tt>
+     */
+    private static final String computeFingerprint(
+            org.bouncycastle.asn1.x509.Certificate certificate,
+            String hashFunction)
+    {
+        try
+        {
+            AlgorithmIdentifier digAlgId
+                = new DefaultDigestAlgorithmIdentifierFinder().find(
+                        hashFunction.toUpperCase());
+            Digest digest = BcDefaultDigestProvider.INSTANCE.get(digAlgId);
+            byte[] in = certificate.getEncoded(ASN1Encoding.DER);
+            byte[] out = new byte[digest.getDigestSize()];
+
+            digest.update(in, 0, in.length);
+            digest.doFinal(out, 0);
+
+            return toHex(out);
+        }
+        catch (Throwable t)
+        {
+            if (t instanceof ThreadDeath)
+            {
+                throw (ThreadDeath) t;
+            }
+            else
+            {
+                logger.error("Failed to generate certificate fingerprint!", t);
+                if (t instanceof RuntimeException)
+                    throw (RuntimeException) t;
+                else
+                    throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Initializes a new <tt>DtlsTransformEngine</tt> instance to be associated
+     * with and used by this <tt>DtlsControlImpl</tt> instance.
+     *
+     * @return a new <tt>DtlsTransformEngine</tt> instance to be associated with
+     * and used by this <tt>DtlsControlImpl</tt> instance
+     */
+    protected DtlsTransformEngine createTransformEngine()
+    {
+        DtlsTransformEngine transformEngine = new DtlsTransformEngine(this);
+
+        transformEngine.setConnector(connector);
+        transformEngine.setDtlsProtocol(dtlsProtocol);
+        return transformEngine;
+    }
+
+    /**
+     * Generates a new subject for a self-signed certificate to be generated by
+     * <tt>DtlsControlImpl</tt>.
+     * 
+     * @return an <tt>X500Name</tt> which is to be used as the subject of a
+     * self-signed certificate to be generated by <tt>DtlsControlImpl</tt>
+     */
+    private static X500Name generateCN()
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        String applicationName
+            = System.getProperty(Version.PNAME_APPLICATION_NAME);
+        String applicationVersion
+            = System.getProperty(Version.PNAME_APPLICATION_VERSION);
+        StringBuilder cn = new StringBuilder();
+
+        if (!StringUtils.isNullOrEmpty(applicationName, true))
+            cn.append(applicationName);
+        if (!StringUtils.isNullOrEmpty(applicationVersion, true))
+        {
+            if (cn.length() != 0)
+                cn.append(' ');
+            cn.append(applicationVersion);
+        }
+        if (cn.length() == 0)
+            cn.append(DtlsControlImpl.class.getName());
+        builder.addRDN(BCStyle.CN, cn.toString());
+
+        return builder.build();
+    }
+
+    /**
+     * Generates a new pair of private and public keys.
+     *
+     * @return a new pair of private and public keys
+     */
+    private static AsymmetricCipherKeyPair generateKeyPair()
+    {
+        RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
+
+        generator.init(
+                new RSAKeyGenerationParameters(
+                        new BigInteger("10001", 16),
+                        new SecureRandom(),
+                        1024,
+                        80));
+        return generator.generateKeyPair();
+    }
+
+    /**
+     * Generates a new self-signed certificate with a specific subject and a
+     * specific pair of private and public keys.
+     *
+     * @param subject the subject (and issuer) of the new certificate to be
+     * generated
+     * @param keyPair the pair of private and public keys of the certificate to
+     * be generated
+     * @return a new self-signed certificate with the specified <tt>subject</tt>
+     * and <tt>keyPair</tt>
+     */
+    private static org.bouncycastle.asn1.x509.Certificate
+        generateX509Certificate(
+                X500Name subject,
+                AsymmetricCipherKeyPair keyPair)
+    {
+        try
+        {
+            long now = System.currentTimeMillis();
+            Date notBefore = new Date(now - ONE_DAY);
+            Date notAfter = new Date(now + 6 * ONE_DAY);
+            X509v3CertificateBuilder builder
+                = new X509v3CertificateBuilder(
+                        /* issuer */ subject,
+                        /* serial */ BigInteger.valueOf(now),
+                        notBefore,
+                        notAfter,
+                        subject,
+                        /* publicKeyInfo */
+                            SubjectPublicKeyInfoFactory
+                                .createSubjectPublicKeyInfo(
+                                    keyPair.getPublic()));
+            AlgorithmIdentifier sigAlgId
+                = new DefaultSignatureAlgorithmIdentifierFinder()
+                    .find("SHA1withRSA");
+            AlgorithmIdentifier digAlgId
+                = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+            ContentSigner signer
+                = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
+                    .build(keyPair.getPrivate());
+
+            return builder.build(signer).toASN1Structure();
+        }
+        catch (Throwable t)
+        {
+            if (t instanceof ThreadDeath)
+                throw (ThreadDeath) t;
+            else
+            {
+                logger.error(
+                        "Failed to generate self-signed X.509 certificate",
+                        t);
+                if (t instanceof RuntimeException)
+                    throw (RuntimeException) t;
+                else
+                    throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * The private and public keys of the <tt>certificate</tt> of this instance.
+     *
+     * @return the private and public keys of the <tt>certificate</tt> of this
+     * instance
+     */
+    AsymmetricCipherKeyPair getKeyPair()
+    {
+        return keyPair;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getSecureCommunicationStatus()
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    /**
+     * Determines the hash function i.e. the digest algorithm of the signature
+     * algorithm of a specific certificate.
+     *
+     * @param certificate the certificate the hash function of which is to be
+     * determined
+     * @return the hash function of the specified <tt>certificate</tt>
+     */
+    private static String findHashFunction(
+            org.bouncycastle.asn1.x509.Certificate certificate)
+    {
+        try
+        {
+            AlgorithmIdentifier sigAlgId = certificate.getSignatureAlgorithm();
+            AlgorithmIdentifier digAlgId
+                = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+            return
+                BcDefaultDigestProvider.INSTANCE
+                    .get(digAlgId)
+                        .getAlgorithmName()
+                            .toLowerCase();
+        }
+        catch (Throwable t)
+        {
+            if (t instanceof ThreadDeath)
+            {
+                throw (ThreadDeath) t;
+            }
+            else
+            {
+                logger.warn(
+                        "Failed to find the hash function of the signature"
+                            + " algorithm of a certificate!",
+                        t);
+                if (t instanceof RuntimeException)
+                    throw (RuntimeException) t;
+                else
+                    throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Gets the certificate with which the local endpoint represented by this
+     * instance authenticates its ends of DTLS sessions.
+     *
+     * @return the certificate with which the local endpoint represented by this
+     * instance authenticates its ends of DTLS sessions.
+     */
+    org.bouncycastle.crypto.tls.Certificate getCertificate()
+    {
+        return certificate;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLocalFingerprint()
+    {
+        return localFingerprint;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getLocalFingerprintHashFunction()
+    {
+        return localFingerprintHashFunction;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>DtlsControlImpl</tt> always returns
+     * <tt>true</tt>.
+     */
+    public boolean requiresSecureSignalingTransport()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setConnector(AbstractRTPConnector connector)
+    {
+        if (this.connector != connector)
+        {
+            this.connector = connector;
+
+            DtlsTransformEngine transformEngine = this.transformEngine;
+
+            if (transformEngine != null)
+                transformEngine.setConnector(this.connector);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDtlsProtocol(int dtlsProtocol)
+    {
+        if (this.dtlsProtocol != dtlsProtocol)
+        {
+            /*
+             * TODO At the time of this writing, changing the DTLS protocol is
+             * not supported.
+             */
+            if ((this.dtlsProtocol == DTLS_CLIENT_PROTOCOL)
+                    || (this.dtlsProtocol == DTLS_SERVER_PROTOCOL))
+                return;
+
+            this.dtlsProtocol = dtlsProtocol;
+
+            DtlsTransformEngine transformEngine = this.transformEngine;
+
+            if (transformEngine != null)
+                transformEngine.setDtlsProtocol(this.dtlsProtocol);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setRemoteFingerprints(Map<String,String> remoteFingerprints)
+    {
+        if (remoteFingerprints == null)
+            throw new NullPointerException("remoteFingerprints");
+
+        synchronized (this)
+        {
+            this.remoteFingerprints = remoteFingerprints;
+            notifyAll();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void start(MediaType mediaType)
+    {
+        DtlsTransformEngine transformEngine = getTransformEngine();
+
+        if (transformEngine != null)
+            transformEngine.start(mediaType);
+    }
+
+    /**
+     * Gets the <tt>String</tt> representation of a fingerprint specified in the
+     * form of an array of <tt>byte</tt>s in accord with RFC 4572.
+     *
+     * @param fingerprint an array of <tt>bytes</tt> which represents a
+     * fingerprint the <tt>String</tt> representation in accord with RFC 4572
+     * of which is to be returned 
+     * @return the <tt>String</tt> representation in accord with RFC 4572 of the
+     * specified <tt>fingerprint</tt>
+     */
+    private static String toHex(byte[] fingerprint)
+    {
+        if (fingerprint.length == 0)
+            throw new IllegalArgumentException("fingerprint");
+
+        char[] chars = new char[3 * fingerprint.length - 1];
+
+        for (int f = 0, fLast = fingerprint.length - 1, c = 0;
+                f <= fLast;
+                f++)
+        {
+            int b = fingerprint[f] & 0xff;
+
+            chars[c++] = HEX_ENCODE_TABLE[b >>> 4];
+            chars[c++] = HEX_ENCODE_TABLE[b & 0x0f];
+            if (f != fLast)
+                chars[c++] = ':';
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Verifies and validates a specific certificate against the fingerprints
+     * presented by the remote endpoint via the signaling path.
+     *
+     * @param certificate the certificate to be verified and validated against
+     * the fingerprints presented by the remote endpoint via the signaling path
+     * @throws Exception if the specified <tt>certificate</tt> failed to verify
+     * and validate against the fingerprints presented by the remote endpoint
+     * via the signaling path
+     */
+    void verifyAndValidateCertificate(
+            org.bouncycastle.asn1.x509.Certificate certificate)
+        throws Exception
+    {
+        /*
+         * RFC 4572 "Connection-Oriented Media Transport over the Transport
+         * Layer Security (TLS) Protocol in the Session Description Protocol
+         * (SDP)" defines that "[a] certificate fingerprint MUST be computed
+         * using the same one-way hash function as is used in the certificate's
+         * signature algorithm."
+         */
+        String hashFunction = findHashFunction(certificate);
+        String fingerprint = computeFingerprint(certificate, hashFunction);
+
+        /*
+         * As RFC 5763 "Framework for Establishing a Secure Real-time Transport
+         * Protocol (SRTP) Security Context Using Datagram Transport Layer
+         * Security (DTLS)" states, "the certificate presented during the DTLS
+         * handshake MUST match the fingerprint exchanged via the signaling path
+         * in the SDP."
+         */
+        String remoteFingerprint;
+
+        synchronized (this)
+        {
+            if (disposed)
+                throw new IllegalStateException("disposed");
+            else
+                remoteFingerprint = remoteFingerprints.get(hashFunction);
+        }
+        if (!remoteFingerprint.equals(fingerprint))
+        {
+            throw new IOException(
+                    "Fingerprint " + remoteFingerprint
+                        + " does not match the " + hashFunction
+                        + "-hashed certificate " + fingerprint + "!");
+        }
+    }
+
+    /**
+     * Verifies and validates a specific certificate against the fingerprints
+     * presented by the remote endpoint via the signaling path.
+     *
+     * @param certificate the certificate to be verified and validated against
+     * the fingerprints presented by the remote endpoint via the signaling path
+     * @throws Exception if the specified <tt>certificate</tt> failed to verify
+     * and validate against the fingerprints presented by the remote endpoint
+     * via the signaling path
+     */
+    void verifyAndValidateCertificate(
+            org.bouncycastle.crypto.tls.Certificate certificate)
+        throws Exception
+    {
+        org.bouncycastle.asn1.x509.Certificate[] certificateList
+            = certificate.getCertificateList();
+
+        if (certificateList.length == 0)
+            throw new IllegalArgumentException("certificate.certificateList");
+
+        for (org.bouncycastle.asn1.x509.Certificate x509Certificate
+                : certificateList)
+        {
+            verifyAndValidateCertificate(x509Certificate);
+        }
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java
new file mode 100644
index 0000000000000000000000000000000000000000..582905330a797b9d8535a53f284aed8333a1ed86
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java
@@ -0,0 +1,626 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import java.io.*;
+import java.security.*;
+
+import org.bouncycastle.crypto.tls.*;
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.transform.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements {@link PacketTransformer} for DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class DtlsPacketTransformer
+    implements PacketTransformer
+{
+    /**
+     * The length of the header of a DTLS record.
+     */
+    static final int DTLS_RECORD_HEADER_LENGTH = 13;
+
+    /**
+     * The number of milliseconds a <tt>DtlsPacketTransform</tt> is to wait on
+     * its {@link #dtlsProtocol} in order to receive a packet.
+     */
+    private static final int DTLS_TRANSPORT_RECEIVE_WAITMILLIS = 1;
+
+    /**
+     * The <tt>Logger</tt> used by the <tt>DtlsPacketTransformer</tt> class and
+     * its instances to print debug information.
+     */
+    private static final Logger logger
+        = Logger.getLogger(DtlsPacketTransformer.class);
+
+    /**
+     * Determines whether a specific array of <tt>byte</tt>s appears to contain
+     * a DTLS record.
+     *
+     * @param buf the array of <tt>byte</tt>s to be analyzed
+     * @param off the offset within <tt>buf</tt> at which the analysis is to
+     * start
+     * @param len the number of bytes within <tt>buf</tt> starting at
+     * <tt>off</tt> to be analyzed
+     * @return <tt>true</tt> if the specified <tt>buf</tt> appears to contain a
+     * DTLS record
+     */
+    public static boolean isDtlsRecord(byte[] buf, int off, int len)
+    {
+        boolean b = false;
+
+        if (len >= DTLS_RECORD_HEADER_LENGTH)
+        {
+            short type = TlsUtils.readUint8(buf, off);
+
+            switch (type)
+            {
+            case ContentType.alert:
+            case ContentType.application_data:
+            case ContentType.change_cipher_spec:
+            case ContentType.handshake:
+                int major = buf[off + 1] & 0xff;
+                int minor = buf[off + 2] & 0xff;
+                ProtocolVersion version = null;
+
+                if ((major == ProtocolVersion.DTLSv10.getMajorVersion())
+                        && (minor == ProtocolVersion.DTLSv10.getMinorVersion()))
+                {
+                    version = ProtocolVersion.DTLSv10;
+                }
+                if ((version == null)
+                        && (major == ProtocolVersion.DTLSv12.getMajorVersion())
+                        && (minor == ProtocolVersion.DTLSv12.getMinorVersion()))
+                {
+                    version = ProtocolVersion.DTLSv12;
+                }
+                if (version != null)
+                {
+                    int length = TlsUtils.readUint16(buf, off + 11);
+
+                    if (DTLS_RECORD_HEADER_LENGTH + length <= len)
+                        b = true;
+                }
+                break;
+            default:
+                /*
+                 * Unless a new ContentType has been defined by the Bouncy
+                 * Castle Crypto APIs, the specified buf does not represent a
+                 * DTLS record.
+                 */
+                break;
+            }
+        }
+        return b;
+    }
+
+    /**
+     * The ID of the component which this instance works for/is associated with.
+     */
+    private final int componentID;
+
+    /**
+     * The background <tt>Thread</tt> which initializes {@link #dtlsTransport}.
+     */
+    private Thread connectThread;
+
+    /**
+     * The <tt>RTPConnector</tt> which uses this <tt>PacketTransformer</tt>.
+     */
+    private AbstractRTPConnector connector;
+
+    /**
+     * The <tt>DatagramTransport</tt> implementation which adapts
+     * {@link #connector} and this <tt>PacketTransformer</tt> to the terms of
+     * the Bouncy Castle Crypto APIs.
+     */
+    private DatagramTransportImpl datagramTransport;
+
+    /**
+     * The DTLS protocol according to which this <tt>DtlsPacketTransformer</tt>
+     * is to act either as a DTLS server or a DTLS client.
+     */
+    private int dtlsProtocol;
+
+    /**
+     * The <tt>DTLSTransport</tt> through which the actual packet
+     * transformations are being performed by this instance.
+     */
+    private DTLSTransport dtlsTransport;
+
+    /**
+     * The <tt>MediaType</tt> of the stream which this instance works for/is
+     * associated with.
+     */
+    private MediaType mediaType;
+
+    /**
+     * The <tt>TransformEngine</tt> which has initialized this instance.
+     */
+    private final DtlsTransformEngine transformEngine;
+
+    /**
+     * Initializes a new <tt>DtlsPacketTransformer</tt> instance.
+     *
+     * @param transformEngine the <tt>TransformEngine</tt> which is initializing
+     * the new instance
+     * @param componentID the ID of the component for which the new instance is
+     * to work
+     */
+    public DtlsPacketTransformer(
+            DtlsTransformEngine transformEngine,
+            int componentID)
+    {
+        this.transformEngine = transformEngine;
+        this.componentID = componentID;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void close()
+    {
+        setConnector(null);
+        setMediaType(null);
+    }
+
+    /**
+     * Closes {@link #datagramTransport} if it is non-<tt>null</tt> and logs and
+     * swallows any <tt>IOException</tt>.
+     */
+    private void closeDatagramTransport()
+    {
+        if (datagramTransport != null)
+        {
+            try
+            {
+                datagramTransport.close();
+            }
+            catch (IOException ioe)
+            {
+                /*
+                 * DatagramTransportImpl has no reason to fail because it is
+                 * merely an adapter of #connector and this PacketTransformer to
+                 * the terms of the Bouncy Castle Crypto API.
+                 */
+                logger.error(
+                        "Failed to (properly) close "
+                            + datagramTransport.getClass(),
+                        ioe);
+            }
+            datagramTransport = null;
+        }
+    }
+
+    /**
+     * Gets the <tt>DtlsControl</tt> implementation associated with this
+     * instance.
+     *
+     * @return the <tt>DtlsControl</tt> implementation associated with this
+     * instance
+     */
+    DtlsControlImpl getDtlsControl()
+    {
+        return getTransformEngine().getDtlsControl();
+    }
+
+    /**
+     * Gets the <tt>TransformEngine</tt> which has initialized this instance.
+     *
+     * @return the <tt>TransformEngine</tt> which has initialized this instance
+     */
+    DtlsTransformEngine getTransformEngine()
+    {
+        return transformEngine;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public RawPacket reverseTransform(RawPacket pkt)
+    {
+        byte[] buf = pkt.getBuffer();
+        int off = pkt.getOffset();
+        int len = pkt.getLength();
+
+        if (isDtlsRecord(buf, off, len))
+        {
+            boolean receive;
+
+            synchronized (this)
+            {
+                if (datagramTransport == null)
+                {
+                    receive = false;
+                }
+                else
+                {
+                    datagramTransport.queue(buf, off, len);
+                    receive = true;
+                }
+            }
+            if (receive)
+            {
+                DTLSTransport dtlsTransport = this.dtlsTransport;
+
+                if (dtlsTransport == null)
+                {
+                    /*
+                     * The specified pkt looks like a DTLS record and it has
+                     * been consumed for the purposes of the secure channel
+                     * represented by this PacketTransformer.
+                     */
+                    pkt = null;
+                }
+                else
+                {
+                    try
+                    {
+                        int receiveLimit = dtlsTransport.getReceiveLimit();
+                        int delta = receiveLimit - len;
+
+                        if (delta > 0)
+                        {
+                            pkt.grow(delta);
+                            buf = pkt.getBuffer();
+                            off = pkt.getOffset();
+                            len = pkt.getLength();
+                        }
+                        else if (delta < 0)
+                        {
+                            pkt.shrink(-delta);
+                            buf = pkt.getBuffer();
+                            off = pkt.getOffset();
+                            len = pkt.getLength();
+                        }
+
+                        int received
+                            = dtlsTransport.receive(
+                                buf, off, len,
+                                DTLS_TRANSPORT_RECEIVE_WAITMILLIS);
+
+                        if (received <= 0)
+                        {
+                            // No application data was decoded.
+                            pkt = null;
+                        }
+                        else
+                        {
+                            delta = len - received;
+                            if (delta > 0)
+                                pkt.shrink(delta);
+                        }
+                    }
+                    catch (IOException ioe)
+                    {
+                        pkt = null;
+                        logger.error("Failed to decode a DTLS record!", ioe);
+                    }
+                }
+            }
+            else
+            {
+                /*
+                 * The specified pkt looks like a DTLS record but it is
+                 * unexpected in the current state of the secure channels
+                 * represented by this PacketTransformer.
+                 */
+                pkt = null;
+            }
+        }
+        else
+        {
+            /*
+             * The specified pkt does not look like a DTLS record so it is not a
+             * valid packet on the secure channel represented by this
+             * PacketTransformer.
+             */
+            pkt = null;
+        }
+        return pkt;
+    }
+
+    /**
+     * Runs in {@link #connectThread} to initialize {@link #dtlsTransport}.
+     *
+     * @param dtlsProtocol
+     * @param tlsPeer
+     * @param datagramTransport
+     */
+    private void runInConnectThread(
+            DTLSProtocol dtlsProtocol,
+            TlsPeer tlsPeer,
+            DatagramTransport datagramTransport)
+    {
+        DTLSTransport dtlsTransport = null;
+
+        if (dtlsProtocol instanceof DTLSClientProtocol)
+        {
+            DTLSClientProtocol dtlsClientProtocol
+                = (DTLSClientProtocol) dtlsProtocol;
+            TlsClient tlsClient = (TlsClient) tlsPeer;
+
+            try
+            {
+                dtlsTransport
+                    = dtlsClientProtocol.connect(
+                            tlsClient, 
+                            datagramTransport);
+            }
+            catch (IOException ioe)
+            {
+                logger.error(
+                        "Failed to connect this DTLS client to a DTLS server!",
+                        ioe);
+            }
+        }
+        else if (dtlsProtocol instanceof DTLSServerProtocol)
+        {
+            DTLSServerProtocol dtlsServerProtocol
+                = (DTLSServerProtocol) dtlsProtocol;
+            TlsServer tlsServer = (TlsServer) tlsPeer;
+
+            try
+            {
+                dtlsTransport
+                    = dtlsServerProtocol.accept(
+                            tlsServer,
+                            datagramTransport);
+            }
+            catch (IOException ioe)
+            {
+                logger.error(
+                        "Failed to accept a connection from a DTLS client!",
+                        ioe);
+            }
+        }
+        else
+            throw new IllegalStateException("dtlsProtocol");
+
+        synchronized (this)
+        {
+            if (Thread.currentThread().equals(this.connectThread)
+                    && datagramTransport.equals(this.datagramTransport))
+            {
+                this.dtlsTransport = dtlsTransport;
+                notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Sets the <tt>RTPConnector</tt> which is to use or uses this
+     * <tt>PacketTransformer</tt>.
+     *
+     * @param connector the <tt>RTPConnector</tt> which is to use or uses this
+     * <tt>PacketTransformer</tt>
+     */
+    void setConnector(AbstractRTPConnector connector)
+    {
+        if (this.connector != connector)
+        {
+            this.connector = connector;
+
+            DatagramTransportImpl datagramTransport = this.datagramTransport;
+
+            if (datagramTransport != null)
+                datagramTransport.setConnector(connector);
+        }
+    }
+
+    /**
+     * Sets the DTLS protocol according to which this
+     * <tt>DtlsPacketTransformer</tt> is to act either as a DTLS server or a
+     * DTLS client.
+     *
+     * @param dtlsProtocol {@link DtlsControl#DTLS_CLIENT_PROTOCOL} to have this
+     * <tt>DtlsPacketTransformer</tt> act as a DTLS client or
+     * {@link DtlsControl#DTLS_SERVER_PROTOCOL} to have this
+     * <tt>DtlsPacketTransformer</tt> act as a DTLS server
+     */
+    void setDtlsProtocol(int dtlsProtocol)
+    {
+        if (this.dtlsProtocol != dtlsProtocol)
+        {
+            this.dtlsProtocol = dtlsProtocol;
+        }
+    }
+
+    /**
+     * Sets the <tt>MediaType</tt> of the stream which this instance is to work
+     * for/be associated with.
+     *
+     * @param mediaType the <tt>MediaType</tt> of the stream which this instance
+     * is to work for/be associated with
+     */
+    synchronized void setMediaType(MediaType mediaType)
+    {
+        if (this.mediaType != mediaType)
+        {
+            if (this.mediaType != null)
+                stop();
+
+            this.mediaType = mediaType;
+
+            if (this.mediaType != null)
+                start();
+        }
+    }
+
+    /**
+     * Starts this <tt>PacketTransformer</tt>.
+     */
+    private synchronized void start()
+    {
+        if (this.datagramTransport != null)
+        {
+            if ((this.connectThread == null) && (dtlsTransport == null))
+            {
+                logger.warn(
+                        getClass().getName()
+                            + " has been started but has failed to establish"
+                            + " the DTLS connection!");
+            }
+            return;
+        }
+
+        int dtlsProtocol = this.dtlsProtocol;
+
+        if ((dtlsProtocol != DtlsControl.DTLS_CLIENT_PROTOCOL)
+                && (dtlsProtocol != DtlsControl.DTLS_SERVER_PROTOCOL))
+            throw new IllegalStateException("dtlsProtocol");
+
+        AbstractRTPConnector connector = this.connector;
+
+        if (connector == null)
+            throw new NullPointerException("connector");
+
+        SecureRandom secureRandom = new SecureRandom();
+        final DTLSProtocol dtlsProtocolObj;
+        final TlsPeer tlsPeer;
+
+        if (dtlsProtocol == DtlsControl.DTLS_CLIENT_PROTOCOL)
+        {
+            dtlsProtocolObj = new DTLSClientProtocol(secureRandom);
+            tlsPeer = new TlsClientImpl(this);
+        }
+        else
+        {
+            dtlsProtocolObj = new DTLSServerProtocol(secureRandom);
+            tlsPeer = new TlsServerImpl(this);
+        }
+
+        final DatagramTransportImpl datagramTransport
+            = new DatagramTransportImpl(componentID);
+
+        datagramTransport.setConnector(connector);
+
+        Thread connectThread
+            = new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        runInConnectThread(
+                                dtlsProtocolObj,
+                                tlsPeer,
+                                datagramTransport);
+                    }
+                    finally
+                    {
+                        if (Thread.currentThread().equals(
+                                DtlsPacketTransformer.this.connectThread))
+                        {
+                            DtlsPacketTransformer.this.connectThread = null;
+                        }
+                    }
+                }
+            };
+
+        connectThread.setDaemon(true);
+        connectThread.setName(
+                DtlsPacketTransformer.class.getName() + ".connectThread");
+
+        this.connectThread = connectThread;
+        this.datagramTransport = datagramTransport;
+
+        boolean started = false;
+
+        try
+        {
+            connectThread.start();
+            started = true;
+        }
+        finally
+        {
+            if (!started)
+            {
+                if (connectThread.equals(this.connectThread))
+                    this.connectThread = null;
+                if (datagramTransport.equals(this.datagramTransport))
+                    this.datagramTransport = null;
+            }
+        }
+
+        notifyAll();
+    }
+
+    /**
+     * Stops this <tt>PacketTransformer</tt>.
+     */
+    private synchronized void stop()
+    {
+        if (connectThread != null)
+            connectThread = null;
+        if (dtlsTransport != null)
+        {
+            try
+            {
+                dtlsTransport.close();
+            }
+            catch (IOException ioe)
+            {
+                logger.error(
+                        "Failed to (properly) close "
+                            + dtlsTransport.getClass(),
+                        ioe);
+            }
+            dtlsTransport = null;
+        }
+        closeDatagramTransport();
+
+        notifyAll();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public RawPacket transform(RawPacket pkt)
+    {
+        byte[] buf = pkt.getBuffer();
+        int off = pkt.getOffset();
+        int len = pkt.getLength();
+
+        /*
+         * If the specified pkt represents a DTLS record, then it should pass
+         * through this PacketTransformer (e.g. it has been sent through
+         * DatagramPacketImpl).
+         */
+        if (!isDtlsRecord(buf, off, len))
+        {
+            /*
+             * The specified pkt will pass through this PacketTransformer only
+             * if it gets transformed into a DTLS record.
+             */
+            pkt = null;
+
+            DTLSTransport dtlsTransport = this.dtlsTransport;
+
+            if (dtlsTransport != null)
+            {
+                try
+                {
+                    dtlsTransport.send(buf, off, len);
+                }
+                catch (IOException ioe)
+                {
+                    logger.error(
+                            "Failed to send application data over DTLS"
+                                + " transport!",
+                            ioe);
+                }
+            }
+        }
+        return pkt;
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5a765084ae6eb75e750efabaa53fc6d5cda876b
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java
@@ -0,0 +1,225 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import org.ice4j.ice.*;
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.transform.*;
+import org.jitsi.service.neomedia.*;
+
+/**
+ * Implements {@link SrtpControl.TransformEngine} (and, respectively,
+ * {@link org.jitsi.impl.neomedia.transform.TransformEngine}) for DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class DtlsTransformEngine
+    implements SrtpControl.TransformEngine
+{
+    /**
+     * The <tt>RTPConnector</tt> which uses this <tt>TransformEngine</tt>.
+     */
+    private AbstractRTPConnector connector;
+
+    /**
+     * The <tt>DtlsControl</tt> which has initialized this instance.
+     */
+    private final DtlsControlImpl dtlsControl;
+
+    /**
+     * The DTLS protocol according to which this <tt>DtlsTransformEngine</tt> is
+     * to act either as a DTLS server or a DTLS client.
+     */
+    private int dtlsProtocol;
+
+    /**
+     * The <tt>MediaType</tt> of the stream which this instance works for/is
+     * associated with.
+     */
+    private MediaType mediaType;
+
+    /**
+     * The <tt>PacketTransformer</tt>s of this <tt>TransformEngine</tt> for
+     * data/RTP and control/RTCP packets.
+     */
+    private final DtlsPacketTransformer[] packetTransformers
+        = new DtlsPacketTransformer[2];
+
+    /**
+     * Initializes a new <tt>DtlsTransformEngine</tt> instance.
+     */
+    public DtlsTransformEngine(DtlsControlImpl dtlsControl)
+    {
+        this.dtlsControl = dtlsControl;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void cleanup()
+    {
+        for (int i = 0; i < packetTransformers.length; i++)
+        {
+            DtlsPacketTransformer packetTransformer = packetTransformers[i];
+
+            if (packetTransformer != null)
+            {
+                packetTransformer.close();
+                packetTransformers[i] = null;
+            }
+        }
+
+        setConnector(null);
+        setMediaType(null);
+    }
+
+    /**
+     * Initializes a new <tt>DtlsPacketTransformer</tt> instance which is to
+     * work on control/RTCP or data/RTP packets.
+     *
+     * @param componentID the ID of the component for which the new instance is
+     * to work
+     * @return a new <tt>DtlsPacketTransformer</tt> instance which is to work on
+     * control/RTCP or data/RTP packets (in accord with <tt>data</tt>)
+     */
+    private DtlsPacketTransformer createPacketTransformer(int componentID)
+    {
+        DtlsPacketTransformer packetTransformer
+            = new DtlsPacketTransformer(this, componentID);
+
+        packetTransformer.setConnector(connector);
+        packetTransformer.setDtlsProtocol(dtlsProtocol);
+        packetTransformer.setMediaType(mediaType);
+        return packetTransformer;
+    }
+
+    /**
+     * Gets the <tt>DtlsControl</tt> which has initialized this instance.
+     *
+     * @return the <tt>DtlsControl</tt> which has initialized this instance
+     */
+    DtlsControlImpl getDtlsControl()
+    {
+        return dtlsControl;
+    }
+
+    /**
+     * Gets the <tt>PacketTransformer</tt> of this <tt>TransformEngine</tt>
+     * which is to work or works for the component with a specific ID.
+     *
+     * @param componentID the ID of the component for which the returned
+     * <tt>PacketTransformer</tt> is to work or works
+     * @return the <tt>PacketTransformer</tt>, if any, which is to work or works
+     * for the component with the specified <tt>componentID</tt>
+     */
+    private DtlsPacketTransformer getPacketTransformer(int componentID)
+    {
+        int index = componentID - 1;
+        DtlsPacketTransformer packetTransformer = packetTransformers[index];
+
+        if (packetTransformer == null)
+        {
+            packetTransformer = createPacketTransformer(componentID);
+            if (packetTransformer != null)
+                packetTransformers[index] = packetTransformer;
+        }
+        return packetTransformer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PacketTransformer getRTCPTransformer()
+    {
+        return getPacketTransformer(Component.RTCP);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PacketTransformer getRTPTransformer()
+    {
+        return getPacketTransformer(Component.RTP);
+    }
+
+    /**
+     * Sets the <tt>RTPConnector</tt> which is to use or uses this
+     * <tt>TransformEngine</tt>.
+     *
+     * @param connector the <tt>RTPConnector</tt> which is to use or uses this
+     * <tt>TransformEngine</tt>
+     */
+    void setConnector(AbstractRTPConnector connector)
+    {
+        if (this.connector != connector)
+        {
+            this.connector = connector;
+
+            for (DtlsPacketTransformer packetTransformer : packetTransformers)
+            {
+                if (packetTransformer != null)
+                    packetTransformer.setConnector(this.connector);
+            }
+        }
+    }
+
+    /**
+     * Sets the DTLS protocol according to which this
+     * <tt>DtlsTransformEngine</tt> is to act either as a DTLS server or a DTLS
+     * client.
+     *
+     * @param dtlsProtocol {@link DtlsControl#DTLS_CLIENT_PROTOCOL} to have this
+     * <tt>DtlsTransformEngine</tt> act as a DTLS client or
+     * {@link DtlsControl#DTLS_SERVER_PROTOCOL} to have this
+     * <tt>DtlsTransformEngine</tt> act as a DTLS server
+     */
+    void setDtlsProtocol(int dtlsProtocol)
+    {
+        if (this.dtlsProtocol != dtlsProtocol)
+        {
+            this.dtlsProtocol = dtlsProtocol;
+
+            for (DtlsPacketTransformer packetTransformer : packetTransformers)
+            {
+                if (packetTransformer != null)
+                    packetTransformer.setDtlsProtocol(this.dtlsProtocol);
+            }
+        }
+    }
+
+    /**
+     * Sets the <tt>MediaType</tt> of the stream which this instance is to work
+     * for/be associated with.
+     *
+     * @param mediaType the <tt>MediaType</tt> of the stream which this instance
+     * is to work for/be associated with
+     */
+    private void setMediaType(MediaType mediaType)
+    {
+        if (this.mediaType != mediaType)
+        {
+            this.mediaType = mediaType;
+
+            for (DtlsPacketTransformer packetTransformer : packetTransformers)
+            {
+                if (packetTransformer != null)
+                    packetTransformer.setMediaType(this.mediaType);
+            }
+        }
+    }
+
+    /**
+     * Starts this instance in the sense that it becomes fully operational.
+     *
+     * @param mediaType the <tt>MediaType</tt> of the stream which this instance
+     * is to work for/be associated with
+     */
+    void start(MediaType mediaType)
+    {
+        setMediaType(mediaType);
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa75730b1c137941c80db6bc70919af4193bdd46
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java
@@ -0,0 +1,145 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import java.io.*;
+
+import org.bouncycastle.crypto.tls.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements {@link TlsClient} for the purposes of supporting DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class TlsClientImpl
+    extends DefaultTlsClient
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>TlsClientImpl</tt> class and its
+     * instances to print debug information.
+     */
+    private static final Logger logger = Logger.getLogger(TlsClientImpl.class);
+
+    private final TlsAuthentication authentication
+        = new TlsAuthenticationImpl();
+
+    /**
+     * The <tt>PacketTransformer</tt> which has initialized this instance.
+     */
+    private final DtlsPacketTransformer packetTransformer;
+
+    /**
+     * Initializes a new <tt>TlsClientImpl</tt> instance.
+     *
+     * @param packetTransformer the <tt>PacketTransformer</tt> which is
+     * initializing the new instance
+     */
+    public TlsClientImpl(DtlsPacketTransformer packetTransformer)
+    {
+        this.packetTransformer = packetTransformer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized TlsAuthentication getAuthentication()
+        throws IOException
+    {
+        return authentication;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>TlsClientImpl</tt> always returns
+     * <tt>ProtocolVersion.DTLSv10</tt> because <tt>ProtocolVersion.DTLSv12</tt>
+     * does not work with the Bouncy Castle Crypto APIs at the time of this
+     * writing.
+     */
+    @Override
+    public ProtocolVersion getClientVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    /**
+     * Gets the <tt>DtlsControl</tt> implementation associated with this
+     * instance.
+     *
+     * @return the <tt>DtlsControl</tt> implementation associated with this
+     * instance
+     */
+    private DtlsControlImpl getDtlsControl()
+    {
+        return packetTransformer.getDtlsControl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    /**
+     * Implements {@link TlsAuthentication} for the purposes of supporting
+     * DTLS-SRTP.
+     *
+     * @author Lyubomir Marinov
+     */
+    private class TlsAuthenticationImpl
+        implements TlsAuthentication
+    {
+        private TlsCredentials clientCredentials;
+
+        /**
+         * {@inheritDoc}
+         */
+        public TlsCredentials getClientCredentials(
+                CertificateRequest certificateRequest)
+            throws IOException
+        {
+            if (clientCredentials == null)
+            {
+                DtlsControlImpl dtlsControl = getDtlsControl();
+
+                clientCredentials
+                    = new DefaultTlsSignerCredentials(
+                            context,
+                            dtlsControl.getCertificate(),
+                            dtlsControl.getKeyPair().getPrivate());
+            }
+            return clientCredentials;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void notifyServerCertificate(Certificate serverCertificate)
+            throws IOException
+        {
+            try
+            {
+                getDtlsControl().verifyAndValidateCertificate(
+                        serverCertificate);
+            }
+            catch (Exception e)
+            {
+                logger.error(
+                        "Failed to verify and/or validate server certificate!",
+                        e);
+                if (e instanceof IOException)
+                    throw (IOException) e;
+                else
+                    throw new IOException(e);
+            }
+        }
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a12a41b9dd163b88a46f12c16bc314129f0b6737
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.transform.dtls;
+
+import java.io.*;
+
+import org.bouncycastle.crypto.tls.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements {@link TlsServer} for the purposes of supporting DTLS-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public class TlsServerImpl
+    extends DefaultTlsServer
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>TlsServerImpl</tt> class and its
+     * instances to print debug information.
+     */
+    private static final Logger logger = Logger.getLogger(TlsServerImpl.class);
+
+    private final CertificateRequest certificateRequest
+        = new CertificateRequest(
+                new short[] { ClientCertificateType.rsa_sign },
+                /* certificateAuthorities */ null);
+
+    /**
+     * The <tt>PacketTransformer</tt> which has initialized this instance.
+     */
+    private final DtlsPacketTransformer packetTransformer;
+
+    private TlsSignerCredentials rsaSignerCredentials;
+
+    /**
+     * Initializes a new <tt>TlsServerImpl</tt> instance.
+     *
+     * @param packetTransformer the <tt>PacketTransformer</tt> which is
+     * initializing the new instance
+     */
+    public TlsServerImpl(DtlsPacketTransformer packetTransformer)
+    {
+        this.packetTransformer = packetTransformer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CertificateRequest getCertificateRequest()
+    {
+        return certificateRequest;
+    }
+
+    /**
+     * Gets the <tt>DtlsControl</tt> implementation associated with this
+     * instance.
+     *
+     * @return the <tt>DtlsControl</tt> implementation associated with this
+     * instance
+     */
+    private DtlsControlImpl getDtlsControl()
+    {
+        return packetTransformer.getDtlsControl();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>TlsServerImpl</tt> always returns
+     * <tt>ProtocolVersion.DTLSv10</tt> because <tt>ProtocolVersion.DTLSv12</tt>
+     * does not work with the Bouncy Castle Crypto APIs at the time of this
+     * writing.
+     */
+    @Override
+    protected ProtocolVersion getMaximumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected TlsSignerCredentials getRSASignerCredentials()
+        throws IOException
+    {
+        if (rsaSignerCredentials == null)
+        {
+            DtlsControlImpl dtlsControl = getDtlsControl();
+
+            rsaSignerCredentials
+                = new DefaultTlsSignerCredentials(
+                        context,
+                        dtlsControl.getCertificate(),
+                        dtlsControl.getKeyPair().getPrivate());
+        }
+        return rsaSignerCredentials;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void notifyClientCertificate(Certificate clientCertificate)
+        throws IOException
+    {
+        try
+        {
+            getDtlsControl().verifyAndValidateCertificate(clientCertificate);
+        }
+        catch (Exception e)
+        {
+            logger.error(
+                    "Failed to verify and/or validate client certificate!",
+                    e);
+            if (e instanceof IOException)
+                throw (IOException) e;
+            else
+                throw new IOException(e);
+        }
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java b/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java
index 12aa6763686f30ba568d3516a3642656ee660811..6c20f5167d18966b102bce90b8e23ea88c506dcb 100644
--- a/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java
+++ b/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java
@@ -40,6 +40,36 @@ public class StatisticsEngine
      */
     public static final String RTP_STAT_PREFIX = "rtpstat:";
 
+    /**
+     * Determines whether a specific <tt>RawPacket</tt> appears to represent an
+     * RTCP packet.
+     *
+     * @param pkt the <tt>RawPacket</tt> to be examined
+     * @return <tt>true</tt> if the specified <tt>pkt</tt> appears to represent
+     * an RTCP packet
+     */
+    private static boolean isRTCP(RawPacket pkt)
+    {
+        int len = pkt.getLength();
+        boolean b = false;
+
+        if (len >= 4)
+        {
+            byte[] buf = pkt.getBuffer();
+            int off = pkt.getOffset();
+            int v = (buf[off] & 0xc0) >>> 6;
+
+            if (v == RTCPHeader.VERSION)
+            {
+                int length = (buf[off + 2] << 8) + (buf[off + 3] << 0);
+
+                if (length <= len)
+                    b = true;
+            }
+        }
+        return b;
+    }
+
     /**
      * Number of lost packets reported.
      */
@@ -202,6 +232,10 @@ public RawPacket transform(RawPacket pkt)
     {
         try
         {
+            // SRTP may send non-RTCP packets.
+            if (!isRTCP(pkt))
+                return pkt;
+
             numberOfSenderReports++;
 
             byte[] data = pkt.getBuffer();
diff --git a/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java b/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java
index 6bea28d1457013ecc8927c68cab14fa7f51086c8..80b217a110930f1ff0cdcf6af9309e67229c7ad4 100644
--- a/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java
+++ b/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java
@@ -11,7 +11,6 @@
 import java.util.*;
 
 import org.jitsi.impl.neomedia.*;
-import org.jitsi.impl.neomedia.transform.*;
 import org.jitsi.impl.neomedia.transform.zrtp.*;
 import org.jitsi.service.neomedia.*;
 import org.jitsi.service.neomedia.event.*;
@@ -25,6 +24,7 @@
  * @author Ingo Bauersachs
  */
 public class SDesControlImpl
+    extends AbstractSrtpControl<SDesTransformEngine>
     implements SDesControl
 {
     /**
@@ -38,17 +38,18 @@ public class SDesControlImpl
     private final List<String> supportedCryptoSuites = new ArrayList<String>(3);
 
     private SrtpCryptoAttribute[] attributes;
-    private SDesTransformEngine engine;
+
     private SrtpSDesFactory sdesFactory;
     private SrtpCryptoAttribute selectedInAttribute;
     private SrtpCryptoAttribute selectedOutAttribute;
-    private SrtpListener srtpListener;
 
     /**
      * SDESControl
      */
     public SDesControlImpl()
     {
+        super(SrtpControlType.SDES);
+
         {
             enabledCryptoSuites.add(SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80);
             enabledCryptoSuites.add(SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32);
@@ -74,15 +75,6 @@ public void nextBytes(byte[] bytes)
                 });
     }
 
-    public void cleanup()
-    {
-        if (engine != null)
-        {
-            engine.close();
-            engine = null;
-        }
-    }
-
     public SrtpCryptoAttribute getInAttribute()
     {
         return selectedInAttribute;
@@ -107,12 +99,7 @@ public SrtpCryptoAttribute getOutAttribute()
 
     public boolean getSecureCommunicationStatus()
     {
-        return engine != null;
-    }
-
-    public SrtpListener getSrtpListener()
-    {
-        return srtpListener;
+        return transformEngine != null;
     }
 
     public Iterable<String> getSupportedCryptoSuites()
@@ -120,16 +107,18 @@ public Iterable<String> getSupportedCryptoSuites()
         return Collections.unmodifiableList(supportedCryptoSuites);
     }
 
-    public TransformEngine getTransformEngine()
+    /**
+     * Initializes a new <tt>SDesTransformEngine</tt> instance to be associated
+     * with and used by this <tt>SDesControlImpl</tt> instance.
+     *
+     * @return a new <tt>SDesTransformEngine</tt> instance to be associated with
+     * and used by this <tt>SDesControlImpl</tt> instance
+     * @see AbstractSrtpControl#createTransformEngine()
+     */
+    protected SDesTransformEngine createTransformEngine()
     {
-        if(engine == null)
-        {
-            engine
-                = new SDesTransformEngine(
-                        selectedInAttribute,
-                        selectedOutAttribute);
-        }
-        return engine;
+        return
+            new SDesTransformEngine(selectedInAttribute, selectedOutAttribute);
     }
 
     /**
@@ -217,7 +206,13 @@ public SrtpCryptoAttribute responderSelectAttribute(
         return null;
     }
 
-    public void setConnector(AbstractRTPConnector newValue)
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>SDesControlImpl</tt> does nothing because
+     * <tt>SDesControlImpl</tt> does not utilize the <tt>RTPConnector</tt>.
+     */
+    public void setConnector(AbstractRTPConnector connector)
     {
     }
 
@@ -228,26 +223,9 @@ public void setEnabledCiphers(Iterable<String> ciphers)
             enabledCryptoSuites.add(c);
     }
 
-    /**
-     * Not used.
-     *
-     * @param masterSession not used.
-     */
-    public void setMasterSession(boolean masterSession)
-    {
-    }
-
-    public void setMultistream(SrtpControl master)
-    {
-    }
-
-    public void setSrtpListener(SrtpListener srtpListener)
-    {
-        this.srtpListener = srtpListener;
-    }
-
     public void start(MediaType type)
     {
+        SrtpListener srtpListener = getSrtpListener();
         // in srtp the started and security event is one after another in some
         // other security mechanisms (e.g. zrtp) there can be started and no
         // security one or security timeout event
diff --git a/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java
index 017e7e26bec55b8dc3a3c0f7569a53f8c81570d1..6339a566d35a4ff460fde8686b919f16cdbd6ccc 100644
--- a/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java
+++ b/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java
@@ -8,6 +8,7 @@
 
 import org.jitsi.impl.neomedia.transform.*;
 import org.jitsi.impl.neomedia.transform.srtp.*;
+import org.jitsi.service.neomedia.*;
 
 import ch.imvs.sdes4j.srtp.*;
 
@@ -17,7 +18,7 @@
  * @author Ingo Bauersachs
  */
 public class SDesTransformEngine
-    implements TransformEngine
+    implements SrtpControl.TransformEngine
 {
     private SRTPTransformer srtpTransformer;
     private SRTCPTransformer srtcpTransformer;
@@ -36,7 +37,10 @@ public SDesTransformEngine(SrtpCryptoAttribute inAttribute,
         srtcpTransformer = new SRTCPTransformer(forwardCtx, reverseCtx);
     }
 
-    public void close()
+    /**
+     * {@inheritDoc}
+     */
+    public void cleanup()
     {
         if (srtpTransformer != null)
             srtpTransformer.close();
diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java b/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java
index 2d0d824b709d1d20127b9ca66986a7f79116bfca..6e30e27dc37d8345bf145787ba54d9acddff380a 100644
--- a/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java
+++ b/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java
@@ -10,7 +10,6 @@
 
 import java.util.*;
 
-import org.jitsi.impl.neomedia.*;
 import org.jitsi.service.libjitsi.*;
 import org.jitsi.service.neomedia.event.*;
 import org.jitsi.service.protocol.event.*;
diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java
index ece8f2d1e3e29066dd7901e72b5b780f46257a78..290a78b64c50e06cfe166ef4f58c89eabb24d04a 100644
--- a/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java
+++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java
@@ -7,6 +7,7 @@
 package org.jitsi.impl.neomedia.transform.zrtp;
 
 import gnu.java.zrtp.*;
+import gnu.java.zrtp.utils.*;
 import gnu.java.zrtp.zidfile.*;
 
 import java.io.*;
@@ -17,10 +18,9 @@
 import org.jitsi.impl.neomedia.transform.srtp.*;
 import org.jitsi.service.fileaccess.*;
 import org.jitsi.service.libjitsi.*;
+import org.jitsi.service.neomedia.*;
 import org.jitsi.util.*;
 
-import gnu.java.zrtp.utils.*;
-
 /**
  * JMF extension/connector to support GNU ZRTP4J.
  *
@@ -121,7 +121,7 @@
  *   zrtpEngine = transConnector.getEngine();
  *   zrtpEngine.setUserCallback(new MyCallback());
  *   if (!zrtpEngine.initialize(&quot;test_t.zid&quot;))
- *       System.out.println(&quot;iniatlize failed&quot;);
+ *       System.out.println(&quot;initialize failed&quot;);
  *
  *   // initialize the RTPManager using the ZRTP connector
  *
@@ -144,12 +144,11 @@
  * describes overloaded methods and a possible different behaviour.
  *
  * @author Werner Dittmann &lt;Werner.Dittmann@t-online.de>
- *
  */
 public class ZRTPTransformEngine
-    implements  TransformEngine,
-                PacketTransformer,
-                ZrtpCallback
+    implements SrtpControl.TransformEngine,
+               PacketTransformer,
+               ZrtpCallback
 {
     /**
      * Very simple Timeout provider class.
@@ -647,7 +646,7 @@ public void stopZrtp()
             zrtpEngine = null;
             started = false;
         }
-        // The SRTP transformer are usually already closed durin security-off
+        // The SRTP transformer are usually already closed during security-off
         // processing. Check here again just in case ...
         if (srtpOutTransformer != null)
         {
@@ -671,6 +670,8 @@ public void stopZrtp()
      */
     public void cleanup()
     {
+        stopZrtp();
+
         if (timeoutProvider != null)
         {
             timeoutProvider.stopRun();
@@ -803,7 +804,7 @@ else if (zPkt.hasMagic())
     /**
      * The callback method required by the ZRTP implementation.
      * First allocate space to hold the complete ZRTP packet, copy
-     * the message part in its place, the initalize the header, counter,
+     * the message part in its place, the initialize the header, counter,
      * SSRC and crc.
      *
      * @param data The ZRTP packet data
diff --git a/src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java
similarity index 97%
rename from src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java
rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java
index 795fa0d16947383a3d8ac0596c4f2d05dc48188a..d606a1d894b8b58ffc8eae4e56048c06dacf3e1f 100644
--- a/src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java
+++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java
@@ -3,7 +3,7 @@
  *
  * Distributable under LGPL license. See terms of license at gnu.org.
  */
-package org.jitsi.impl.neomedia;
+package org.jitsi.impl.neomedia.transform.zrtp;
 
 import gnu.java.zrtp.*;
 
diff --git a/src/org/jitsi/impl/neomedia/ZrtpControlImpl.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java
similarity index 80%
rename from src/org/jitsi/impl/neomedia/ZrtpControlImpl.java
rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java
index 4f8961a29a7246fe31ce7ba500097a00764da5e3..feb0119d685232436ac8eca3a0fe1fb0498d5834 100644
--- a/src/org/jitsi/impl/neomedia/ZrtpControlImpl.java
+++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java
@@ -4,16 +4,15 @@
  * Distributable under LGPL license.
  * See terms of license at gnu.org.
  */
-package org.jitsi.impl.neomedia;
+package org.jitsi.impl.neomedia.transform.zrtp;
 
 import gnu.java.zrtp.*;
 import gnu.java.zrtp.utils.*;
 
 import java.util.*;
 
-import org.jitsi.impl.neomedia.transform.zrtp.*;
+import org.jitsi.impl.neomedia.*;
 import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.event.*;
 
 /**
  * Controls zrtp in the MediaStream.
@@ -21,6 +20,7 @@
  * @author Damian Minkov
  */
 public class ZrtpControlImpl
+    extends AbstractSrtpControl<ZRTPTransformEngine>
     implements ZrtpControl
 {
     /**
@@ -47,21 +47,12 @@ public static enum ZRTPCustomInfoCodes
      */
     private AbstractRTPConnector zrtpConnector = null;
 
-    /**
-     * The zrtp engine control by this ZrtpControl.
-     */
-    private ZRTPTransformEngine zrtpEngine = null;
-
-    /**
-     * The listener interested in security events about zrtp.
-     */
-    private SrtpListener zrtpListener = null;
-
     /**
      * Creates the control.
      */
-    ZrtpControlImpl()
+    public ZrtpControlImpl()
     {
+        super(SrtpControlType.ZRTP);
     }
 
     /**
@@ -69,13 +60,8 @@ public static enum ZRTPCustomInfoCodes
      */
     public void cleanup()
     {
-        if(zrtpEngine != null)
-        {
-            zrtpEngine.stopZrtp();
-            zrtpEngine.cleanup();
-        }
+        super.cleanup();
 
-        zrtpEngine = null;
         zrtpConnector = null;
     }
 
@@ -97,7 +83,10 @@ public String getCipherString()
      */
     public int getCurrentProtocolVersion()
     {
-        return ((zrtpEngine != null) ? zrtpEngine.getCurrentProtocolVersion() : 0);
+        ZRTPTransformEngine zrtpEngine = this.transformEngine;
+
+        return
+            (zrtpEngine != null) ? zrtpEngine.getCurrentProtocolVersion() : 0;
     }
 
     /**
@@ -136,7 +125,10 @@ public String[] getHelloHashSep(int index)
      */
     public int getNumberSupportedVersions()
     {
-        return ((zrtpEngine != null) ? zrtpEngine.getNumberSupportedVersions(): 0);
+        ZRTPTransformEngine zrtpEngine = this.transformEngine;
+
+        return
+            (zrtpEngine != null) ? zrtpEngine.getNumberSupportedVersions() : 0;
     }
 
     /**
@@ -150,6 +142,8 @@ public int getNumberSupportedVersions()
      *         from our peer. If peer's hello hash is not available return null.
      */
     public String getPeerHelloHash() {
+        ZRTPTransformEngine zrtpEngine = this.transformEngine;
+
         if (zrtpEngine != null)
             return zrtpEngine.getPeerHelloHash();
         else
@@ -189,6 +183,8 @@ public String getPeerZidString()
      */
     public boolean getSecureCommunicationStatus()
     {
+        ZRTPTransformEngine zrtpEngine = this.transformEngine;
+
         return
             (zrtpEngine != null) && zrtpEngine.getSecureCommunicationStatus();
     }
@@ -205,16 +201,6 @@ public String getSecurityString()
         return getTransformEngine().getUserCallback().getSecurityString();
     }
 
-    /**
-     * Returns the <tt>ZrtpListener</tt> which listens for security events.
-     *
-     * @return the <tt>ZrtpListener</tt> which listens for  security events
-     */
-    public SrtpListener getSrtpListener()
-    {
-        return this.zrtpListener;
-    }
-
     /**
      * Returns the timeout value that will we will wait
      * and fire timeout secure event if call is not secured.
@@ -230,24 +216,25 @@ public long getTimeoutValue()
     }
 
     /**
-     * Returns the zrtp engine currently used by this stream.
-     * @return the zrtp engine
+     * Initializes a new <tt>ZRTPTransformEngine</tt> instance to be associated
+     * with and used by this <tt>ZrtpControlImpl</tt> instance.
+     *
+     * @return a new <tt>ZRTPTransformEngine</tt> instance to be associated with
+     * and used by this <tt>ZrtpControlImpl</tt> instance
      */
-    public ZRTPTransformEngine getTransformEngine()
+    protected ZRTPTransformEngine createTransformEngine()
     {
-        if(zrtpEngine == null)
-        {
-            zrtpEngine = new ZRTPTransformEngine();
-
-            // NOTE: set paranoid mode before initializing
-            // zrtpEngine.setParanoidMode(paranoidMode);
-            zrtpEngine.initialize(
-                    "GNUZRTP4J.zid",
-                    false,
-                    ZrtpConfigureUtils.getZrtpConfiguration());
-            zrtpEngine.setUserCallback(new SecurityEventManager(this));
-        }
-        return zrtpEngine;
+        ZRTPTransformEngine transformEngine = new ZRTPTransformEngine();
+
+        // NOTE: set paranoid mode before initializing
+        // zrtpEngine.setParanoidMode(paranoidMode);
+        transformEngine.initialize(
+                "GNUZRTP4J.zid",
+                false,
+                ZrtpConfigureUtils.getZrtpConfiguration());
+        transformEngine.setUserCallback(new SecurityEventManager(this));
+
+        return transformEngine;
     }
 
     /*
@@ -285,8 +272,10 @@ public void setConnector(AbstractRTPConnector connector)
 
     /**
      * When in multistream mode, enables the master session.
-     * @param masterSession whether current control, controls the master session.
+     *
+     * @param masterSession whether current control, controls the master session
      */
+    @Override
     public void setMasterSession(boolean masterSession)
     {
         // by default its not master, change only if set to be master
@@ -297,14 +286,14 @@ public void setMasterSession(boolean masterSession)
     }
 
     /**
-     * Start multi-stream ZRTP sessions.
-     *
-     * After the ZRTP Master (DH) session reached secure state the SCCallback
-     * calls this method to start the multi-stream ZRTP sessions.
+     * Start multi-stream ZRTP sessions. After the ZRTP Master (DH) session
+     * reached secure state the SCCallback calls this method to start the
+     * multi-stream ZRTP sessions. Enable auto-start mode (auto-sensing) to the
+     * engine.
      *
-     * enable auto-start mode (auto-sensing) to the engine.
      * @param master master SRTP data
      */
+    @Override
     public void setMultistream(SrtpControl master)
     {
         if(master == null || master == this)
@@ -335,16 +324,6 @@ public void setSASVerification(boolean verified)
             engine.resetSASVerified();
     }
 
-    /**
-     * Sets a <tt>ZrtpListener</tt> that will listen for zrtp security events.
-     *
-     * @param zrtpListener the <tt>ZrtpListener</tt> to set
-     */
-    public void setSrtpListener(SrtpListener zrtpListener)
-    {
-        this.zrtpListener = zrtpListener;
-    }
-
     /**
      * Starts and enables zrtp in the stream holding this control.
      * @param mediaType the media type of the stream this control controls.
@@ -387,12 +366,12 @@ public void start(MediaType mediaType)
             // it may happen when using multistreams, audio has inited
             // and started video
             // initially engine has value enableZrtp = false
-            zrtpAutoStart = zrtpEngine.isEnableZrtp();
+            zrtpAutoStart = transformEngine.isEnableZrtp();
             securityEventManager.setSessionType(sessionType);
         }
         engine.setConnector(zrtpConnector);
 
-        securityEventManager.setSrtpListener(zrtpListener);
+        securityEventManager.setSrtpListener(getSrtpListener());
 
         // tells the engine whether to autostart(enable)
         // zrtp communication, if false it just passes packets without
diff --git a/src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java
similarity index 99%
rename from src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java
rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java
index 9e91e6d2795b6d47013e1b25d079481dbef9cf70..4ca3192b630b0accca0bb9db04708575f3d10981 100644
--- a/src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java
+++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java
@@ -4,7 +4,7 @@
  * Distributable under LGPL license.
  * See terms of license at gnu.org.
  */
-package org.jitsi.impl.neomedia;
+package org.jitsi.impl.neomedia.transform.zrtp;
 
 import gnu.java.zrtp.utils.*;
 
diff --git a/src/org/jitsi/service/neomedia/AbstractSrtpControl.java b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f248c7281178732742a80ce205b001fe3bf2715
--- /dev/null
+++ b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java
@@ -0,0 +1,121 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.service.neomedia;
+
+import org.jitsi.service.neomedia.event.*;
+
+/**
+ * Provides an abstract, base implementation of {@link SrtpControl} to
+ * facilitate implementers.
+ *
+ * @author Lyubomir Marinov
+ */
+public abstract class AbstractSrtpControl<T extends SrtpControl.TransformEngine>
+    implements SrtpControl
+{
+    private final SrtpControlType srtpControlType;
+
+    /**
+     * The <tt>SrtpListener</tt> listening to security events (to be) fired by
+     * this <tt>SrtpControl</tt> instance.
+     */
+    private SrtpListener srtpListener;
+
+    protected T transformEngine;
+
+    /**
+     * Initializes a new <tt>AbstractSrtpControl</tt> instance with a specific
+     * <tt>SrtpControlType</tt>.
+     *
+     * @param srtpControlType the <tt>SrtpControlType</tt> of the new instance
+     */
+    protected AbstractSrtpControl(SrtpControlType srtpControlType)
+    {
+        if (srtpControlType == null)
+            throw new NullPointerException("srtpControlType");
+
+        this.srtpControlType = srtpControlType;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>AbstractSrtpControl</tt> cleans up its
+     * associated <tt>TransformEngine</tt> (if any).
+     */
+    public void cleanup()
+    {
+        if (transformEngine != null)
+        {
+            transformEngine.cleanup();
+            transformEngine = null;
+        }
+    }
+
+    /**
+     * Initializes a new <tt>TransformEngine</tt> instance to be associated with
+     * and used by this <tt>SrtpControl</tt> instance.
+     *
+     * @return a new <tt>TransformEngine</tt> instance to be associated with and
+     * used by this <tt>SrtpControl</tt> instance
+     */
+    protected abstract T createTransformEngine();
+
+    /**
+     * {@inheritDoc}
+     */
+    public SrtpControlType getSrtpControlType()
+    {
+        return srtpControlType;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SrtpListener getSrtpListener()
+    {
+        return srtpListener;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public T getTransformEngine()
+    {
+        if (transformEngine == null)
+            transformEngine = createTransformEngine();
+        return transformEngine;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>AbstractSrtpControl</tt> does nothing because
+     * support for multistream mode is the exception rather than the norm. 
+     */
+    public void setMasterSession(boolean masterSession)
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>AbstractSrtpControl</tt> does nothing because
+     * support for multistream mode is the exception rather than the norm. 
+     */
+    public void setMultistream(SrtpControl master)
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setSrtpListener(SrtpListener srtpListener)
+    {
+        this.srtpListener = srtpListener;
+    }
+}
diff --git a/src/org/jitsi/service/neomedia/DtlsControl.java b/src/org/jitsi/service/neomedia/DtlsControl.java
new file mode 100644
index 0000000000000000000000000000000000000000..fbc6eb92a500b5a8753ad85ee619bb15f2a59f95
--- /dev/null
+++ b/src/org/jitsi/service/neomedia/DtlsControl.java
@@ -0,0 +1,93 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.service.neomedia;
+
+import java.util.*;
+
+
+/**
+ * Implements {@link SrtpControl} for DTSL-SRTP.
+ *
+ * @author Lyubomir Marinov
+ */
+public interface DtlsControl
+    extends SrtpControl
+{
+    /**
+     * The DTLS protocol to be set on a <tt>DtlsControl</tt> instance via
+     * {@link #setDtlsProtocol(int)} to indicate that the <tt>DtlsControl</tt>
+     * is to act as a DTLS client.
+     */
+    public static final int DTLS_CLIENT_PROTOCOL = 1;
+
+    /**
+     * The DTLS protocol to be set on a <tt>DtlsControl</tt> instance via
+     * {@link #setDtlsProtocol(int)} to indicate that the <tt>DtlsControl</tt>
+     * is to act as a DTLS server.
+     */
+    public static final int DTLS_SERVER_PROTOCOL = 2;
+
+    /**
+     * The human-readable non-localized name of the (S)RTP transport protocol
+     * represented by <tt>DtlsControl</tt>.
+     */
+    public static final String PROTO_NAME
+        = SrtpControlType.DTLS_SRTP.toString();
+
+    /**
+     * The transport protocol (i.e. <tt>&lt;proto&gt;</tt>) to be specified in
+     * a SDP media description (i.e. <tt>m=</tt> line) in order to denote a
+     * RTP/SAVP stream transported over DTLS with UDP. 
+     */
+    public static final String UDP_TLS_RTP_SAVP = "UDP/TLS/RTP/SAVP";
+
+    /**
+     * The transport protocol (i.e. <tt>&lt;proto&gt;</tt>) to be specified in
+     * a SDP media description (i.e. <tt>m=</tt> line) in order to denote a
+     * RTP/SAVPF stream transported over DTLS with UDP. 
+     */
+    public static final String UDP_TLS_RTP_SAVPF = "UDP/TLS/RTP/SAVPF";
+
+    /**
+     * Gets the fingerprint of the local certificate that this instance uses to
+     * authenticate its ends of DTLS sessions.
+     *
+     * @return the fingerprint of the local certificate that this instance uses
+     * to authenticate its ends of DTLS sessions
+     */
+    public String getLocalFingerprint();
+
+    /**
+     * Gets the hash function with which the fingerprint of the local
+     * certificate is computed i.e. the digest algorithm of the signature
+     * algorithm of the local certificate.
+     * 
+     * @return the hash function with which the fingerprint of the local
+     * certificate is computed
+     */
+    public String getLocalFingerprintHashFunction();
+
+    /**
+     * Sets the DTLS protocol according to which this <tt>DtlsControl</tt> is to
+     * act.
+     *
+     * @param dtlsProtocol {@link #DTLS_CLIENT_PROTOCOL} to have this instance
+     * act as a DTLS client or {@link #DTLS_SERVER_PROTOCOL} to have this
+     * instance act as a DTLS server
+     */
+    public void setDtlsProtocol(int dtlsProtocol);
+
+    /**
+     * Sets the certificate fingerprints presented by the remote endpoint via
+     * the signaling path.
+     * 
+     * @param remoteFingerprints a <tt>Map</tt> of hash functions to certificate
+     * fingerprints that have been presented by the remote endpoint via the
+     * signaling path
+     */
+    public void setRemoteFingerprints(Map<String,String> remoteFingerprints);
+}
diff --git a/src/org/jitsi/service/neomedia/MediaService.java b/src/org/jitsi/service/neomedia/MediaService.java
index 3c2cd651171775d9218c0e4d170fdb9b0cbb14d7..dcd441e5d4007748d2fdec836c7e97d10bc32632 100644
--- a/src/org/jitsi/service/neomedia/MediaService.java
+++ b/src/org/jitsi/service/neomedia/MediaService.java
@@ -166,22 +166,14 @@ public MediaStream createMediaStream(StreamConnector connector,
     public RTPTranslator createRTPTranslator();
 
     /**
-     * Initializes a new <tt>SDesControl</tt> instance which is to control all
-     * SDes options.
+     * Initializes a new <tt>SrtpControl</tt> instance with a specific
+     * <tt>SrtpControlType</tt>.
      *
-     * @return a new <tt>SDesControl</tt> instance which is to control all SDes
-     * options
+     * @param srtpControlType the <tt>SrtpControlType</tt> of the new instance
+     * @return a new <tt>SrtpControl</tt> instance with the specified
+     * <tt>srtpControlType</tt>
      */
-    public SDesControl createSDesControl();
-
-    /**
-     * Initializes a new <tt>ZrtpControl</tt> instance which is to control all
-     * ZRTP options.
-     *
-     * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP
-     * options
-     */
-    public ZrtpControl createZrtpControl();
+    public SrtpControl createSrtpControl(SrtpControlType srtpControlType);
 
     /**
      * Get available <tt>ScreenDevice</tt>s.
diff --git a/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java b/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java
deleted file mode 100644
index 74647a5da95b8a931ea3f2d14025c3cd4a8a3981..0000000000000000000000000000000000000000
--- a/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
- */
-package org.jitsi.service.neomedia;
-
-/**
- * Utility class to combine <tt>MediaType</tt> and <tt>SrtpControlType</tt> as a
- * map key.
- *
- * @author Ingo Bauersachs
- */
-public class MediaTypeSrtpControl implements Comparable<MediaTypeSrtpControl>
-{
-    /**
-     * The media type.
-     */
-    public final MediaType mediaType;
-
-    /**
-     * The SRTP control type.
-     */
-    public final SrtpControlType srtpControlType;
-
-    /**
-     * Creates a new instance of this class.
-     * @param mt The <tt>MediaType</tt> for this key.
-     * @param sct The <tt>SrtpControlType</tt> for this key.
-     */
-    public MediaTypeSrtpControl(MediaType mt, SrtpControlType sct)
-    {
-        mediaType = mt;
-        srtpControlType = sct;
-    }
-
-    @Override
-    public boolean equals(Object obj)
-    {
-        if(obj == null || obj.getClass() != MediaTypeSrtpControl.class)
-            return false;
-
-        MediaTypeSrtpControl other = (MediaTypeSrtpControl)obj;
-        return mediaType == other.mediaType
-            && srtpControlType == other.srtpControlType;
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return mediaType.hashCode() ^ srtpControlType.hashCode();
-    }
-
-    public int compareTo(MediaTypeSrtpControl o)
-    {
-        return getWeight() == o.getWeight() ?
-                0 :
-                getWeight() < o.getWeight() ?
-                    -1 : 1;
-    }
-
-    private int getWeight()
-    {
-        int mtWeight = 0;
-        switch(mediaType)
-        {
-            case AUDIO:
-                mtWeight = 1;
-                break;
-            case VIDEO:
-                mtWeight = 2;
-                break;
-        }
-        int stWeight = 0;
-        switch(srtpControlType)
-        {
-            case ZRTP:
-                stWeight = 1;
-                break;
-            case MIKEY:
-                stWeight = 2;
-                break;
-            case SDES:
-                stWeight = 3;
-                break;
-        }
-        return mtWeight * 10 + stWeight;
-    }
-}
diff --git a/src/org/jitsi/service/neomedia/SDesControl.java b/src/org/jitsi/service/neomedia/SDesControl.java
index c1bd9d7264e200d24be1bfa663d7b36aad95c40c..e56f575f2e447f4fff8531a50ebcf2cfd5c70022 100644
--- a/src/org/jitsi/service/neomedia/SDesControl.java
+++ b/src/org/jitsi/service/neomedia/SDesControl.java
@@ -16,6 +16,12 @@
 public interface SDesControl
     extends SrtpControl
 {
+    /**
+     * The human-readable non-localized name of the (S)RTP transport protocol
+     * represented by <tt>DtlsControl</tt>.
+     */
+    public static final String PROTO_NAME = SrtpControlType.SDES.toString();
+
     /**
      * Name of the config setting that supplies the default enabled cipher
      * suites. Cipher suites are comma-separated.
diff --git a/src/org/jitsi/service/neomedia/SrtpControl.java b/src/org/jitsi/service/neomedia/SrtpControl.java
index 778ac12c1f9f26a73dfb9bd14e1b90aaa9f6a502..86e34d1aaf5d9f0ae3bd0f9bee4d0e7bc8cbb926 100644
--- a/src/org/jitsi/service/neomedia/SrtpControl.java
+++ b/src/org/jitsi/service/neomedia/SrtpControl.java
@@ -7,7 +7,6 @@
 package org.jitsi.service.neomedia;
 
 import org.jitsi.impl.neomedia.*;
-import org.jitsi.impl.neomedia.transform.*;
 import org.jitsi.service.neomedia.event.*;
 
 /**
@@ -17,8 +16,29 @@
  */
 public interface SrtpControl
 {
+    public static final String RTP_SAVP = "RTP/SAVP";
+
+    public static final String RTP_SAVPF = "RTP/SAVPF";
+
+    /**
+     * Adds a <tt>cleanup()</tt> method to
+     * <tt>org.jitsi.impl.neomedia.transform.TransformEngine</tt> which is to go
+     * in hand with the <tt>cleanup()</tt> method of <tt>SrtpControl</tt>.
+     *
+     * @author Lyubomir Marinov
+     */
+    public interface TransformEngine
+        extends org.jitsi.impl.neomedia.transform.TransformEngine
+    {
+        /**
+         * Cleans up this <tt>TransformEngine</tt> and prepares it for garbage
+         * collection.
+         */
+        public void cleanup();
+    }
+
     /**
-     * Cleans up the current SRTP control and its engine.
+     * Cleans up this <tt>SrtpControl</tt> and its <tt>TransformEngine</tt>.
      */
     public void cleanup();
 
@@ -31,6 +51,13 @@ public interface SrtpControl
      */
     public boolean getSecureCommunicationStatus();
 
+    /**
+     * Gets the <tt>SrtpControlType</tt> of this instance.
+     *
+     * @return the <tt>SrtpControlType</tt> of this instance
+     */
+    public SrtpControlType getSrtpControlType();
+
     /**
      * Returns the <tt>SrtpListener</tt> which listens for security events.
      *
diff --git a/src/org/jitsi/service/neomedia/SrtpControlType.java b/src/org/jitsi/service/neomedia/SrtpControlType.java
index 453e1bf8ad99ba9cead47ee75e207b0e71cc9e8c..051f4c4b52c93964bb5f3da436b0217a8c117659 100644
--- a/src/org/jitsi/service/neomedia/SrtpControlType.java
+++ b/src/org/jitsi/service/neomedia/SrtpControlType.java
@@ -11,22 +11,55 @@
  * <tt>SrtpControl</tt> implementations.
  *
  * @author Ingo Bauersachs
+ * @author Lyubomir Marinov
  */
 public enum SrtpControlType
 {
+    /**
+     * Datagram Transport Layer Security (DTLS) Extension to Establish Keys for
+     * the Secure Real-time Transport Protocol (SRTP)
+     */
+    DTLS_SRTP("DTLS-SRTP"),
+
+    /**
+     * Multimedia Internet KEYing (RFC 3830)
+     */
+    MIKEY("MIKEY"),
+
     /**
      * Session Description Protocol (SDP) Security Descriptions for Media
      * Streams (RFC 4568)
      */
-    SDES,
+    SDES("SDES"),
 
     /**
      * ZRTP: Media Path Key Agreement for Unicast Secure RTP (RFC 6189)
      */
-    ZRTP,
+    ZRTP("ZRTP");
 
     /**
-     * Multimedia Internet KEYing (RFC 3830)
+     * The human-readable non-localized name of the (S)RTP transport protocol
+     * represented by this <tt>SrtpControlType</tt> and its respective
+     * <tt>SrtpControl</tt> class.
+     */
+    private final String protoName;
+
+    /**
+     * Initializes a new <tt>SrtpControlType</tt> instance with a specific
+     * human-readable non-localized (S)RTP transport protocol name.
+     *
+     * @param protoName the human-readable non-localized name of the (S)RTP
+     * transport protocol represented by the new instance and its respective
+     * <tt>SrtpControl</tt> class
      */
-    MIKEY
+    private SrtpControlType(String protoName)
+    {
+        this.protoName = protoName;
+    }
+
+    @Override
+    public String toString()
+    {
+        return protoName;
+    }
 }
diff --git a/src/org/jitsi/service/neomedia/ZrtpControl.java b/src/org/jitsi/service/neomedia/ZrtpControl.java
index d9173d896e8a97e53719a32509afccb0d9b71ce7..37a7f3f1bc77762a42f5378014b3251f60e64a89 100644
--- a/src/org/jitsi/service/neomedia/ZrtpControl.java
+++ b/src/org/jitsi/service/neomedia/ZrtpControl.java
@@ -14,6 +14,12 @@
 public interface ZrtpControl
     extends SrtpControl
 {
+    /**
+     * The human-readable non-localized name of the (S)RTP transport protocol
+     * represented by <tt>ZrtpControl</tt>.
+     */
+    public static final String PROTO_NAME = SrtpControlType.ZRTP.toString();
+
     /**
      * Gets the cipher information for the current media stream.
      *