diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index f0e7b7ba0763b87360cb48c3b4766315aad89d5d..34b73a09d96cec187b3b1855b9982db6da6fe0a6 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -853,9 +853,11 @@ IceTransport::waitForData(int comp_id, unsigned int timeout)
     auto& io = compIO_[comp_id];
     std::unique_lock<std::mutex> lk(io.mutex);
     if (!io.cv.wait_for(lk, std::chrono::milliseconds(timeout),
-                        [&io]{ return !io.queue.empty(); })) {
+                        [this, &io]{ return !io.queue.empty() or !isRunning(); })) {
         return 0;
     }
+    if (!isRunning())
+        return -1; // acknowledged as an error
     return io.queue.front().datalen;
 }
 
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index 68f3e160627fa024f84d874a7158a3c75d1266c0..86a9d7d9b1bf2a824887aef51aecf0f5b2dec10b 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -665,9 +665,12 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming)
 
     // Securize a SIP transport with TLS (on top of ICE tranport) and assign the call with it
     auto remote_h = pc.from;
+    auto id(loadIdentity());
+
     tls::TlsParams tlsParams {
         .ca_list = "",
-        .id = loadIdentity(),
+        .cert = id.second,
+        .cert_key = id.first,
         .dh_params = dhParams_,
         .timeout = std::chrono::duration_cast<decltype(tls::TlsParams::timeout)>(TLS_TIMEOUT),
         .cert_check = [remote_h](unsigned status, const gnutls_datum_t* cert_list,
@@ -1209,36 +1212,10 @@ RingAccount::loadValues() const
     return values;
 }
 
-static std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>
-getNewDhParams()
-{
-    using namespace std::chrono;
-    auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL);
-    RING_DBG("Generating DH params with %u bits", bits);
-    auto t1 = high_resolution_clock::now();
-
-    gnutls_dh_params_t new_params_;
-    int ret = gnutls_dh_params_init(&new_params_);
-    if (ret != GNUTLS_E_SUCCESS) {
-        RING_ERR("Error initializing DH params: %s", gnutls_strerror(ret));
-        return {nullptr, gnutls_dh_params_deinit};
-    }
-
-    ret = gnutls_dh_params_generate2(new_params_, bits);
-    if (ret != GNUTLS_E_SUCCESS) {
-        RING_ERR("Error generating DH params: %s", gnutls_strerror(ret));
-        return {nullptr, gnutls_dh_params_deinit};
-    }
-
-    auto time_span = duration_cast<duration<double>>(high_resolution_clock::now() - t1);
-    RING_DBG("Generated DH params with %u bits in %lfs", bits, time_span.count());
-    return {new_params_, gnutls_dh_params_deinit};
-}
-
 void
 RingAccount::generateDhParams()
 {
-    std::packaged_task<decltype(getNewDhParams())()> task(&getNewDhParams);
+    std::packaged_task<decltype(tls::newDhParams)> task(tls::newDhParams);
     dhParams_ = task.get_future();
     std::thread task_td(std::move(task));
     task_td.detach();
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index f9d1190b7289d947a18497bda763b2432136cf2e..e8c600a3f8528240c611c50e3da5c9392655ec2c 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -24,6 +24,7 @@
 #include "config.h"
 #endif
 
+#include "security/tls_session.h"
 #include "sip/sipaccountbase.h"
 
 #include "noncopyable.h"
@@ -374,7 +375,7 @@ class RingAccount : public SIPAccountBase {
          */
         void generateDhParams();
 
-        std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dhParams_;
+        std::shared_future<tls::TlsParams::DhParams> dhParams_;
         std::mutex dhParamsMtx_;
         std::condition_variable dhParamsCv_;
         bool allowPeersFromHistory_;
diff --git a/src/ringdht/sips_transport_ice.cpp b/src/ringdht/sips_transport_ice.cpp
index b62d3f88aaec9977cb1dbecdd51153f05888301f..0cef35de9ce481dac7667601cb174430c0a6b5ee 100644
--- a/src/ringdht/sips_transport_ice.cpp
+++ b/src/ringdht/sips_transport_ice.cpp
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2004-2015 Savoir-faire Linux Inc.
+ *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
  *
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -19,9 +20,14 @@
  */
 
 #include "sips_transport_ice.h"
+
 #include "ice_transport.h"
 #include "manager.h"
+#include "sip/sip_utils.h"
 #include "logger.h"
+#include "intrin.h"
+
+#include <opendht/crypto.h>
 
 #include <gnutls/dtls.h>
 #include <gnutls/abstract.h>
@@ -39,7 +45,6 @@ namespace ring { namespace tls {
 static constexpr int POOL_TP_INIT {512};
 static constexpr int POOL_TP_INC {512};
 static constexpr int TRANSPORT_INFO_LENGTH {64};
-static constexpr int DTLS_MTU {6400};
 
 static void
 sockaddr_to_host_port(pj_pool_t* pool,
@@ -52,74 +57,138 @@ sockaddr_to_host_port(pj_pool_t* pool,
     host_port->port = pj_sockaddr_get_port(addr);
 }
 
+static pj_status_t
+tls_status_from_err(int err)
+{
+    pj_status_t status;
+
+    switch (err) {
+        case GNUTLS_E_SUCCESS:
+            status = PJ_SUCCESS;
+            break;
+        case GNUTLS_E_MEMORY_ERROR:
+            status = PJ_ENOMEM;
+            break;
+        case GNUTLS_E_LARGE_PACKET:
+            status = PJ_ETOOBIG;
+            break;
+        case GNUTLS_E_NO_CERTIFICATE_FOUND:
+            status = PJ_ENOTFOUND;
+            break;
+        case GNUTLS_E_SESSION_EOF:
+            status = PJ_EEOF;
+            break;
+        case GNUTLS_E_HANDSHAKE_TOO_LARGE:
+            status = PJ_ETOOBIG;
+            break;
+        case GNUTLS_E_EXPIRED:
+            status = PJ_EGONE;
+            break;
+        case GNUTLS_E_TIMEDOUT:
+            status = PJ_ETIMEDOUT;
+            break;
+        case GNUTLS_E_PREMATURE_TERMINATION:
+            status = PJ_ECANCELLED;
+            break;
+        case GNUTLS_E_INTERNAL_ERROR:
+        case GNUTLS_E_UNIMPLEMENTED_FEATURE:
+            status = PJ_EBUG;
+            break;
+        case GNUTLS_E_AGAIN:
+        case GNUTLS_E_INTERRUPTED:
+        case GNUTLS_E_REHANDSHAKE:
+            status = PJ_EPENDING;
+            break;
+        case GNUTLS_E_TOO_MANY_EMPTY_PACKETS:
+        case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS:
+        case GNUTLS_E_RECORD_LIMIT_REACHED:
+            status = PJ_ETOOMANY;
+            break;
+        case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
+        case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
+        case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE:
+        case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE:
+        case GNUTLS_E_X509_UNSUPPORTED_EXTENSION:
+        case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION:
+            status = PJ_ENOTSUP;
+            break;
+        case GNUTLS_E_INVALID_SESSION:
+        case GNUTLS_E_INVALID_REQUEST:
+        case GNUTLS_E_INVALID_PASSWORD:
+        case GNUTLS_E_ILLEGAL_PARAMETER:
+        case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION:
+        case GNUTLS_E_UNEXPECTED_PACKET:
+        case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
+        case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
+        case GNUTLS_E_UNWANTED_ALGORITHM:
+        case GNUTLS_E_USER_ERROR:
+            status = PJ_EINVAL;
+            break;
+        default:
+            status = PJ_EUNKNOWN;
+            break;
+    }
+
+    return status;
+}
+
 SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt,
                                    const TlsParams& param,
                                    const std::shared_ptr<ring::IceTransport>& ice,
                                    int comp_id)
-    : pool_  {nullptr, pj_pool_release}
-    , rxPool_ (nullptr, pj_pool_release)
-    , trData_ ()
-    , ice_ (ice)
+    : ice_ (ice)
     , comp_id_ (comp_id)
-    , param_ (param)
-    , tlsThread_(
-        std::bind(&SipsIceTransport::setup, this),
-        std::bind(&SipsIceTransport::loop, this),
-        std::bind(&SipsIceTransport::clean, this))
-    , xcred_ {nullptr, gnutls_certificate_free_credentials}
+    , certCheck_(param.cert_check)
+    , trData_ ()
+    , pool_  {nullptr, pj_pool_release}
+    , rxPool_ (nullptr, pj_pool_release)
 {
     RING_DBG("SipIceTransport@%p {tr=%p}", this, &trData_.base);
 
-    trData_.self = this;
-
     if (not ice or not ice->isRunning())
         throw std::logic_error("ICE transport must exist and negotiation completed");
 
-    pool_.reset(pjsip_endpt_create_pool(endpt, "SipsIceTransport.pool",
-                                        POOL_TP_INIT, POOL_TP_INC));
-    if (not pool_) {
-        RING_ERR("Can't create PJSIP pool");
-        throw std::bad_alloc();
-    }
-    auto pool = pool_.get();
+    trData_.self = this; // up-link for PJSIP callbacks
+
+    pool_ = std::move(sip_utils::smart_alloc_pool(endpt, "SipsIceTransport.pool",
+                                                  POOL_TP_INIT, POOL_TP_INC));
 
     auto& base = trData_.base;
+    std::memset(&rdata_, 0, sizeof(pjsip_rx_data));
+
     pj_ansi_snprintf(base.obj_name, PJ_MAX_OBJ_NAME, "SipsIceTransport");
     base.endpt = endpt;
     base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
-    base.pool = pool;
+    base.pool = pool_.get();
 
-    if (pj_atomic_create(pool, 0, &base.ref_cnt) != PJ_SUCCESS)
+    if (pj_atomic_create(pool_.get(), 0, &base.ref_cnt) != PJ_SUCCESS)
         throw std::runtime_error("Can't create PJSIP atomic.");
 
-    if (pj_lock_create_recursive_mutex(pool, "SipsIceTransport.mutex",
+    if (pj_lock_create_recursive_mutex(pool_.get(), "SipsIceTransport.mutex",
                                        &base.lock) != PJ_SUCCESS)
         throw std::runtime_error("Can't create PJSIP mutex.");
 
-    is_server_ = not ice->isInitiator();
     local_ = ice->getLocalAddress(comp_id);
     remote_ = ice->getRemoteAddress(comp_id);
     pj_sockaddr_cp(&base.key.rem_addr, remote_.pjPtr());
     base.key.type = PJSIP_TRANSPORT_TLS;
     base.type_name = (char*)pjsip_transport_get_type_name((pjsip_transport_type_e)base.key.type);
     base.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)base.key.type);
-    base.info = (char*) pj_pool_alloc(pool, TRANSPORT_INFO_LENGTH);
+    base.info = (char*) pj_pool_alloc(pool_.get(), TRANSPORT_INFO_LENGTH);
 
     char print_addr[PJ_INET6_ADDRSTRLEN+10];
-    pj_ansi_snprintf(base.info, TRANSPORT_INFO_LENGTH, "%s to %s",
-                     base.type_name,
-                     pj_sockaddr_print(remote_.pjPtr(), print_addr,
-                                       sizeof(print_addr), 3));
+    pj_ansi_snprintf(base.info, TRANSPORT_INFO_LENGTH, "%s to %s", base.type_name,
+                     pj_sockaddr_print(remote_.pjPtr(), print_addr, sizeof(print_addr), 3));
     base.addr_len = remote_.getLength();
-    base.dir = PJSIP_TP_DIR_NONE; ///is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+    base.dir = PJSIP_TP_DIR_NONE;
     base.data = nullptr;
 
     /* Set initial local address */
     auto local = ice->getDefaultLocalAddress();
     pj_sockaddr_cp(&base.local_addr, local.pjPtr());
 
-    sockaddr_to_host_port(pool, &base.local_name, &base.local_addr);
-    sockaddr_to_host_port(pool, &base.remote_name, remote_.pjPtr());
+    sockaddr_to_host_port(pool_.get(), &base.local_name, &base.local_addr);
+    sockaddr_to_host_port(pool_.get(), &base.remote_name, remote_.pjPtr());
 
     base.send_msg = [](pjsip_transport *transport,
                        pjsip_tx_data *tdata,
@@ -131,26 +200,31 @@ SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt,
     base.do_shutdown = [](pjsip_transport *transport) -> pj_status_t {
         auto& this_ = reinterpret_cast<TransportData*>(transport)->self;
         RING_DBG("SipsIceTransport@%p: shutdown", this_);
-        this_->shutdown();
+        {
+            // Flush pending state changes and rx packet before shutdown
+            // or pjsip callbacks will crash
+
+            std::lock_guard<std::mutex> lk{this_->stateChangeEventsMutex_};
+            this_->stateChangeEvents_.clear();
+
+            std::lock_guard<std::mutex> lk2(this_->rxMtx_);
+            this_->rxPending_.clear();
+
+            this_->tls_->shutdown();
+        }
         return PJ_SUCCESS;
     };
     base.destroy = [](pjsip_transport *transport) -> pj_status_t {
         auto& this_ = reinterpret_cast<TransportData*>(transport)->self;
-        RING_DBG("SipsIceTransport@%p: destroy", this_);
+        RING_DBG("SipsIceTransport@%p: destroying", this_);
         delete this_;
         return PJ_SUCCESS;
     };
 
     /* Init rdata_ */
     std::memset(&rdata_, 0, sizeof(pjsip_rx_data));
-    rxPool_.reset(pjsip_endpt_create_pool(base.endpt,
-                                          "SipsIceTransport.rxPool",
-                                          PJSIP_POOL_RDATA_LEN,
-                                          PJSIP_POOL_RDATA_LEN));
-    if (not rxPool_) {
-        RING_ERR("Can't create PJSIP rx pool");
-        throw std::bad_alloc();
-    }
+    rxPool_ = std::move(sip_utils::smart_alloc_pool(endpt, "SipsIceTransport.rxPool",
+                                                    PJSIP_POOL_RDATA_LEN, PJSIP_POOL_RDATA_LEN));
     rdata_.tp_info.pool = rxPool_.get();
     rdata_.tp_info.transport = &base;
     rdata_.tp_info.tp_data = this;
@@ -167,210 +241,249 @@ SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt,
     std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info));
     std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info));
 
-    /* Register error subsystem */
-    /*pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER +
-                                              PJ_ERRNO_SPACE_SIZE * 6,
-                                              PJ_ERRNO_SPACE_SIZE,
-                                              &tls_strerror);
-    pj_assert(status == PJ_SUCCESS);*/
+    TlsSession::TlsSessionCallbacks cbs = {
+        .onStateChange = [this](TlsSessionState state){ onTlsStateChange(state); },
+        .onRxData = [this](std::vector<uint8_t>&& buf){ onRxData(std::move(buf)); },
+        .onCertificatesUpdate = [this](const gnutls_datum_t* l, const gnutls_datum_t* r,
+                                       unsigned int n){ onCertificatesUpdate(l, r, n); },
+        .verifyCertificate = [this](gnutls_session_t session){ return verifyCertificate(session); }
+    };
+    tls_.reset(new TlsSession(ice, comp_id, param, cbs));
 
     if (pjsip_transport_register(base.tpmgr, &base) != PJ_SUCCESS)
         throw std::runtime_error("Can't register PJSIP transport.");
 
-    gnutls_priority_init(&priority_cache_,
-                         "SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:%SERVER_PRECEDENCE",
-                         nullptr);
-
-    tlsThread_.start();
     Manager::instance().registerEventHandler((uintptr_t)this, [this]{ handleEvents(); });
 }
 
 SipsIceTransport::~SipsIceTransport()
 {
-    RING_DBG("~SipsIceTransport");
-    auto event = state_ == TlsConnectionState::ESTABLISHED;
-    shutdown();
-    tlsThread_.join();
-    Manager::instance().unregisterEventHandler((uintptr_t)this);
-    handleEvents(); // process latest incoming packets
-
-    pjsip_transport_add_ref(getTransportBase());
-    auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr);
-    if (state_cb && event) {
-        pjsip_transport_state_info state_info;
-        pjsip_tls_state_info tls_info;
-
-        /* Init transport state info */
-        std::memset(&state_info, 0, sizeof(state_info));
-        std::memset(&tls_info, 0, sizeof(tls_info));
-        pj_ssl_sock_info ssl_info;
-        getInfo(&ssl_info);
-        tls_info.ssl_sock_info = &ssl_info;
-        state_info.ext_info = &tls_info;
-        state_info.status = PJ_SUCCESS;
-
-        (*state_cb)(getTransportBase(), PJSIP_TP_STATE_DISCONNECTED, &state_info);
-    }
+    RING_DBG("~SipIceTransport@%p {tr=%p}", this, &trData_.base);
 
-    if (not trData_.base.is_shutdown and not trData_.base.is_destroying)
-        pjsip_transport_shutdown(getTransportBase());
+    auto base = getTransportBase();
+    Manager::instance().unregisterEventHandler((uintptr_t)this);
 
-    pjsip_transport_dec_ref(getTransportBase());
+    // Stop low-level transport first
+    tls_.reset();
 
-    pj_lock_destroy(trData_.base.lock);
-    pj_atomic_destroy(trData_.base.ref_cnt);
+    // If delete not trigged by pjsip_transport_destroy (happen if objet not given to pjsip)
+    if (not base->is_shutdown and not base->is_destroying)
+        pjsip_transport_shutdown(base);
 
-    gnutls_priority_deinit(priority_cache_);
+    pj_lock_destroy(base->lock);
+    pj_atomic_destroy(base->ref_cnt);
+    RING_DBG("~SipIceTransport@%p {tr=%p} bye", this, &trData_.base);
 }
 
-pj_status_t
-SipsIceTransport::startTlsSession()
+void
+SipsIceTransport::handleEvents()
 {
-    RING_DBG("SipsIceTransport::startTlsSession as %s",
-             (is_server_ ? "server" : "client"));
-    int ret;
+    // Notify transport manager about state changes first
+    decltype(stateChangeEvents_) eventDataQueue;
+    {
+        std::lock_guard<std::mutex> lk{stateChangeEventsMutex_};
+        eventDataQueue = std::move(stateChangeEvents_);
+        stateChangeEvents_.clear();
+    }
 
-    ret = gnutls_init(&session_, (is_server_ ? GNUTLS_SERVER : GNUTLS_CLIENT) | GNUTLS_DATAGRAM);
-    if (ret != GNUTLS_E_SUCCESS) {
-        shutdown();
-        return tls_status_from_err(ret);
+    if (auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr)) {
+        for (auto& evdata : eventDataQueue) {
+            evdata.tls_info.ssl_sock_info = &evdata.ssl_info;
+            evdata.state_info.ext_info = &evdata.tls_info;
+            (*state_cb)(&trData_.base, evdata.state, &evdata.state_info);
+        }
+    }
+
+    // Handle TLS -> SIP transport
+    decltype(rxPending_) rx;
+    {
+        std::lock_guard<std::mutex> l(rxMtx_);
+        rx = std::move(rxPending_);
+        rxPending_.clear();
     }
 
-    gnutls_session_set_ptr(session_, this);
-    gnutls_transport_set_ptr(session_, this);
+    for (auto it = rx.begin(); it != rx.end(); ++it) {
+        auto& pck = *it;
+        pj_pool_reset(rdata_.tp_info.pool);
+        pj_gettimeofday(&rdata_.pkt_info.timestamp);
+        rdata_.pkt_info.len = pck.size();
+        std::copy_n(pck.data(), pck.size(), rdata_.pkt_info.packet);
+        auto eaten = pjsip_tpmgr_receive_packet(trData_.base.tpmgr, &rdata_);
 
-    gnutls_priority_set(session_, priority_cache_);
+        // Uncomplet parsing? (may be a partial sip packet received)
+        if (eaten != rdata_.pkt_info.len) {
+            auto npck_it = std::next(it);
+            if (npck_it != rx.end()) {
+                // drop current packet, merge reminder with next one
+                auto& npck = *npck_it;
+                npck.insert(npck.begin(), pck.begin()+eaten, pck.end());
+            } else {
+                // erase eaten part, keep reminder
+                pck.erase(pck.begin(), pck.begin()+eaten);
+                {
+                    std::lock_guard<std::mutex> l(rxMtx_);
+                    rxPending_.splice(rxPending_.begin(), rx, it);
+                }
+                break;
+            }
+        }
+    }
 
-    /* Allocate credentials for handshaking and transmission */
-    gnutls_certificate_credentials_t certCred;
-    ret = gnutls_certificate_allocate_credentials(&certCred);
-    if (ret < 0 or not certCred) {
-        RING_ERR("Can't allocate credentials");
-        shutdown();
-        return PJ_ENOMEM;
+    // Acknowledged SIP -> TLS packet
+    decltype(txAckQueue_) ack_queue;
+    {
+        std::lock_guard<std::mutex> l(txAckQueueMutex_);
+        ack_queue = std::move(txAckQueue_);
+        txAckQueue_.clear();
+    }
+    for (const auto& pair: ack_queue) {
+        auto tdata = pair.first;
+        tdata->op_key.tdata = nullptr;
+        if (tdata->op_key.callback)
+            tdata->op_key.callback(&trData_.base, tdata->op_key.token, pair.second);
     }
+}
 
-    xcred_.reset(certCred);
+void
+SipsIceTransport::pushChangeStateEvent(ChangeStateEventData&& ev)
+{
+    std::lock_guard<std::mutex> lk{stateChangeEventsMutex_};
+    stateChangeEvents_.emplace_back(std::move(ev));
+}
+
+// - DO NOT BLOCK - (Called in TlsSession thread)
+void
+SipsIceTransport::onTlsStateChange(UNUSED TlsSessionState state)
+{
+    if (state == TlsSessionState::ESTABLISHED)
+        updateTransportState(PJSIP_TP_STATE_CONNECTED);
+    else if (state == TlsSessionState::SHUTDOWN)
+        updateTransportState(PJSIP_TP_STATE_DISCONNECTED);
+}
 
-    if (is_server_) {
-        auto& dh_params = param_.dh_params.get();
-        if (dh_params)
-            gnutls_certificate_set_dh_params(certCred, dh_params.get());
-        else
-            RING_ERR("DH params unavaliable");
+// - DO NOT BLOCK - (Called in TlsSession thread)
+void
+SipsIceTransport::onRxData(std::vector<uint8_t>&& buf)
+{
+    std::lock_guard<std::mutex> l(rxMtx_);
+    if (rxPending_.empty())
+        rxPending_.emplace_back(std::move(buf));
+    else {
+        auto& last = rxPending_.back();
+        last.insert(std::end(last), std::begin(buf), std::end(buf));
     }
+}
 
-    gnutls_certificate_set_verify_function(certCred, [](gnutls_session_t session) {
-        auto this_ = reinterpret_cast<SipsIceTransport*>(gnutls_session_get_ptr(session));
-        return this_->verifyCertificate();
-    });
-
-    if (not param_.ca_list.empty()) {
-        /* Load CA if one is specified. */
-        ret = gnutls_certificate_set_x509_trust_file(certCred,
-                                                     param_.ca_list.c_str(),
-                                                     GNUTLS_X509_FMT_PEM);
-        if (ret < 0)
-            ret = gnutls_certificate_set_x509_trust_file(certCred,
-                                                         param_.ca_list.c_str(),
-                                                         GNUTLS_X509_FMT_DER);
-        if (ret < 0)
-            throw std::runtime_error("Can't load CA.");
-        RING_DBG("Loaded %s", param_.ca_list.c_str());
+/* Update local & remote certificates info. This function should be
+ * called after handshake or re-negotiation successfully completed.
+ *
+ * - DO NOT BLOCK - (Called in TlsSession thread)
+ */
+void
+SipsIceTransport::onCertificatesUpdate(const gnutls_datum_t* local_raw,
+                                       const gnutls_datum_t* remote_raw,
+                                       unsigned int remote_count)
+{
+    // local certificate
+    if (local_raw)
+        certGetInfo(pool_.get(), &localCertInfo_, local_raw, 1);
+    else
+        std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info));
+
+    // Remote certificates
+    if (remote_raw)
+        certGetInfo(pool_.get(), &remoteCertInfo_, remote_raw, remote_count);
+    else
+        std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info));
+}
+
+int
+SipsIceTransport::verifyCertificate(gnutls_session_t session)
+{
+    // Support only x509 format
+    if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
+        verifyStatus_ = PJ_SSL_CERT_EINVALID_FORMAT;
+        return GNUTLS_E_CERTIFICATE_ERROR;
     }
-    if (param_.id.first) {
-        /* Load certificate, key and pass */
-        ret = gnutls_certificate_set_x509_key(certCred,
-                                              &param_.id.second->cert, 1,
-                                              param_.id.first->x509_key);
-        if (ret < 0)
-            throw std::runtime_error("Can't load certificate : "
-                                     + std::string(gnutls_strerror(ret)));
+
+    // Store verification status
+    unsigned int status = 0;
+    auto ret = gnutls_certificate_verify_peers2(session, &status);
+    if (ret < 0 or (status & GNUTLS_CERT_SIGNATURE_FAILURE) != 0) {
+        verifyStatus_ = PJ_SSL_CERT_EUNTRUSTED;
+        return GNUTLS_E_CERTIFICATE_ERROR;
     }
 
-    /* Finally set credentials for this session */
-    ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, certCred);
-    if (ret != GNUTLS_E_SUCCESS) {
-        shutdown();
-        return tls_status_from_err(ret);
+    unsigned int cert_list_size = 0;
+    auto cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+    if (cert_list == nullptr) {
+        verifyStatus_ = PJ_SSL_CERT_EISSUER_NOT_FOUND;
+        return GNUTLS_E_CERTIFICATE_ERROR;
     }
 
-    if (is_server_) {
-        /* Require client certificate and valid cookie */
-        gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE);
-        gnutls_dtls_prestate_set(session_, &prestate_);
+    if (certCheck_) {
+        verifyStatus_ = certCheck_(status, cert_list, cert_list_size);
+        if (verifyStatus_ != PJ_SUCCESS)
+            return GNUTLS_E_CERTIFICATE_ERROR;
     }
 
-    gnutls_dtls_set_mtu(session_, DTLS_MTU);
-
-    gnutls_transport_set_vec_push_function(session_, [](gnutls_transport_ptr_t t,
-                                                    const giovec_t* iov,
-                                                    int iovcnt) -> ssize_t {
-        auto this_ = reinterpret_cast<SipsIceTransport*>(t);
-        ssize_t sent = 0;
-        for (int i=0; i<iovcnt; i++) {
-            const giovec_t& dat = *(iov+i);
-            auto ret = this_->tlsSend(dat.iov_base, dat.iov_len);
-            if (ret < 0)
-                return ret;
-            sent += ret;
-        }
-        return sent;
-    });
-    gnutls_transport_set_pull_function(session_, [](gnutls_transport_ptr_t t,
-                                                    void* d,
-                                                    size_t s) -> ssize_t {
-        auto this_ = reinterpret_cast<SipsIceTransport*>(t);
-        return this_->tlsRecv(d, s);
-    });
-    gnutls_transport_set_pull_timeout_function(session_, [](gnutls_transport_ptr_t t,
-                                                            unsigned ms) -> int {
-        auto this_ = reinterpret_cast<SipsIceTransport*>(t);
-        return this_->waitForTlsData(ms);
-    });
-
-    // start handshake
-    handshakeStart_ = clock::now();
-    state_ = TlsConnectionState::HANDSHAKING;
-
-    return PJ_SUCCESS;
+    // notify GnuTLS to continue handshake normally
+    return GNUTLS_E_SUCCESS;
 }
 
 void
-SipsIceTransport::closeTlsSession()
+SipsIceTransport::updateTransportState(pjsip_transport_state state)
 {
-    state_ = TlsConnectionState::DISCONNECTED;
-    if (session_) {
-        gnutls_bye(session_, GNUTLS_SHUT_RDWR);
-        gnutls_deinit(session_);
-        session_ = nullptr;
-    }
+    ChangeStateEventData ev;
+
+    std::memset(&ev.state_info, 0, sizeof(ev.state_info));
+    std::memset(&ev.tls_info, 0, sizeof(ev.tls_info));
 
-    xcred_.reset();
+    ev.state = state;
+    tlsConnected_ = state == PJSIP_TP_STATE_CONNECTED;
+    getInfo(&ev.ssl_info, tlsConnected_);
+    if (tlsConnected_)
+        ev.state_info.status = ev.ssl_info.verify_status ? PJSIP_TLS_ECERTVERIF : PJ_SUCCESS;
+    else
+        ev.state_info.status = PJ_SUCCESS; // TODO: use last gnu error
+
+    pushChangeStateEvent(std::move(ev));
 }
 
 void
-SipsIceTransport::certGetCn(const pj_str_t* gen_name, pj_str_t* cn)
+SipsIceTransport::getInfo(pj_ssl_sock_info* info, bool established)
 {
-    pj_str_t CN_sign = {(char*)"CN=", 3};
-    char *p, *q;
+    std::memset(info, 0, sizeof(*info));
 
-    std::memset(cn, 0, sizeof(*cn));
+    info->established = established;
+    info->proto = PJ_SSL_SOCK_PROTO_DTLS1;
 
-    p = pj_strstr(gen_name, &CN_sign);
-    if (!p)
-        return;
+    pj_sockaddr_cp(&info->local_addr, local_.pjPtr());
 
-    p += 3; /* shift pointer to value part */
-    pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr));
-    q = pj_strchr(cn, ',');
-    if (q)
-        cn->slen = q - p;
+    if (established) {
+        // Cipher Suite Id
+        std::array<uint8_t, 2> cs_id;
+        if (auto cipher_name = tls_->getCurrentCipherSuiteId(cs_id)) {
+            info->cipher = static_cast<pj_ssl_cipher>((cs_id[0] << 8) | cs_id[1]);
+            RING_DBG("[TLS] using cipher %s (0x%02X%02X)", cipher_name, cs_id[0], cs_id[1]);
+        } else
+            RING_ERR("[TLS] Can't find info on used cipher");
+
+        info->local_cert_info = &localCertInfo_;
+        info->remote_cert_info = &remoteCertInfo_;
+        info->verify_status = verifyStatus_;
+
+        pj_sockaddr_cp(&info->remote_addr, remote_.pjPtr());
+    }
+
+    // Last known GnuTLS error code
+    info->last_native_err = GNUTLS_E_SUCCESS;
 }
 
 /* Get certificate info; in case the certificate info is already populated,
  * this function will check if the contents need updating by inspecting the
- * issuer and the serial number. */
+ * issuer and the serial number.
+ */
 void
 SipsIceTransport::certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci,
                               const gnutls_datum_t* crt_raw, size_t crt_raw_num)
@@ -441,7 +554,7 @@ SipsIceTransport::certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci,
         ci->subj_alt_name.entry = \
             (decltype(ci->subj_alt_name.entry))pj_pool_calloc(pool, seq, sizeof(*ci->subj_alt_name.entry));
         if (!ci->subj_alt_name.entry) {
-            last_err_ = GNUTLS_E_MEMORY_ERROR;
+            //last_err_ = GNUTLS_E_MEMORY_ERROR;
             return;
         }
 
@@ -482,471 +595,23 @@ SipsIceTransport::certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci,
     }
 }
 
-
-/* Update local & remote certificates info. This function should be
- * called after handshake or renegotiation successfully completed. */
-void
-SipsIceTransport::certUpdate()
-{
-    /* Get active local certificate */
-    if(const auto local_raw = gnutls_certificate_get_ours(session_))
-        certGetInfo(pool_.get(), &localCertInfo_, local_raw, 1);
-    else
-        std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info));
-
-    unsigned int certslen = 0;
-    if (const auto remote_raw = gnutls_certificate_get_peers(session_, &certslen))
-        certGetInfo(pool_.get(), &remoteCertInfo_, remote_raw, certslen);
-    else
-        std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info));
-}
-
 void
-SipsIceTransport::getInfo(pj_ssl_sock_info* info)
-{
-    std::memset(info, 0, sizeof(*info));
-
-    /* Established flag */
-    info->established = (state_ == TlsConnectionState::ESTABLISHED);
-
-    /* Protocol */
-    info->proto = PJ_SSL_SOCK_PROTO_DTLS1;
-
-    /* Local address */
-    pj_sockaddr_cp(&info->local_addr, local_.pjPtr());
-
-    if (info->established) {
-        unsigned char id[2];
-        gnutls_cipher_algorithm_t cipher;
-        gnutls_cipher_algorithm_t lookup;
-
-        /* Current cipher */
-        cipher = gnutls_cipher_get(session_);
-        for (size_t i=0; ; ++i) {
-            const auto suite = gnutls_cipher_suite_info(i, id, nullptr, &lookup,
-                                                        nullptr, nullptr);
-            if (not suite) {
-                RING_ERR("Can't find info for cipher %s (%d)", gnutls_cipher_get_name(cipher), cipher);
-                break;
-            }
-
-            if (lookup == cipher) {
-                info->cipher = (pj_ssl_cipher) ((id[0] << 8) | id[1]);
-                break;
-            }
-        }
-
-        /* Remote address */
-        pj_sockaddr_cp(&info->remote_addr, remote_.pjPtr());
-
-        /* Certificates info */
-        info->local_cert_info = &localCertInfo_;
-        info->remote_cert_info = &remoteCertInfo_;
-
-        /* Verification status */
-        info->verify_status = verifyStatus_;
-    }
-
-    /* Last known GnuTLS error code */
-    info->last_native_err = last_err_;
-}
-
-pj_status_t
-SipsIceTransport::tryHandshake()
-{
-    RING_DBG("SipsIceTransport::tryHandshake as %s",
-             (is_server_ ? "server" : "client"));
-    pj_status_t status;
-    int ret = gnutls_handshake(session_);
-    if (ret == GNUTLS_E_SUCCESS) {
-        /* System are GO */
-        RING_DBG("SipsIceTransport::tryHandshake : ESTABLISHED");
-        state_ = TlsConnectionState::ESTABLISHED;
-        status = PJ_SUCCESS;
-    } else if (!gnutls_error_is_fatal(ret)) {
-        /* Non fatal error, retry later (busy or again) */
-        status = PJ_EPENDING;
-    } else {
-        /* Fatal error invalidates session, no fallback */
-        RING_ERR("TLS fatal error : %s", gnutls_strerror(ret));
-        status = PJ_EINVAL;
-    }
-    last_err_ = ret;
-    return status;
-}
-
-/* When handshake completed:
- * - notify application
- * - if handshake failed, reset SSL state
- * - return false when SSL socket instance is destroyed by application. */
-bool
-SipsIceTransport::onHandshakeComplete(pj_status_t status)
-{
-    /* Update certificates info on successful handshake */
-    if (status == PJ_SUCCESS) {
-        RING_DBG("Handshake success on remote %s",
-                   remote_.toString(true).c_str());
-        certUpdate();
-    } else {
-        /* Handshake failed destroy ourself silently. */
-        char errmsg[PJ_ERR_MSG_SIZE];
-        RING_ERR("Handshake failed on remote %s: %s",
-                 remote_.toString(true).c_str(),
-                 pj_strerror(status, errmsg, sizeof(errmsg)).ptr);
-    }
-
-    ChangeStateEventData eventData;
-    getInfo(&eventData.ssl_info);
-
-    if (status != PJ_SUCCESS)
-        shutdown(); // to be done after getInfo() call
-
-    // push event to handleEvents()
-    eventData.state_info.status = eventData.ssl_info.verify_status ? PJSIP_TLS_ECERTVERIF : PJ_SUCCESS;
-    eventData.state = (status != PJ_SUCCESS) ? PJSIP_TP_STATE_DISCONNECTED : PJSIP_TP_STATE_CONNECTED;
-
-    {
-        std::lock_guard<std::mutex> lk{stateChangeEventsMutex_};
-        stateChangeEvents_.emplace(std::move(eventData));
-    }
-
-    return status == PJ_SUCCESS;
-}
-
-int
-SipsIceTransport::verifyCertificate()
-{
-    /* Support only x509 format */
-    if (gnutls_certificate_type_get(session_) != GNUTLS_CRT_X509) {
-        verifyStatus_ = PJ_SSL_CERT_EINVALID_FORMAT;
-        return GNUTLS_E_CERTIFICATE_ERROR;
-    }
-
-    /* Store verification status */
-    unsigned int status = 0;
-    auto ret = gnutls_certificate_verify_peers2(session_, &status);
-    if (ret < 0 or (status & GNUTLS_CERT_SIGNATURE_FAILURE) != 0) {
-        verifyStatus_ = PJ_SSL_CERT_EUNTRUSTED;
-        return GNUTLS_E_CERTIFICATE_ERROR;
-    }
-
-    unsigned int cert_list_size = 0;
-    auto cert_list = gnutls_certificate_get_peers(session_, &cert_list_size);
-    if (cert_list == nullptr) {
-        verifyStatus_ = PJ_SSL_CERT_EISSUER_NOT_FOUND;
-        return GNUTLS_E_CERTIFICATE_ERROR;
-    }
-
-    if (param_.cert_check) {
-        verifyStatus_ = param_.cert_check(status, cert_list, cert_list_size);
-        if (verifyStatus_ != PJ_SUCCESS)
-            return GNUTLS_E_CERTIFICATE_ERROR;
-    }
-
-    /* notify GnuTLS to continue handshake normally */
-    return GNUTLS_E_SUCCESS;
-}
-
-void
-SipsIceTransport::handleEvents()
-{
-    // Process state changes first
-    decltype(stateChangeEvents_) eventDataQueue = stateChangeEvents_;
-    {
-        std::lock_guard<std::mutex> lk{stateChangeEventsMutex_};
-        eventDataQueue = std::move(stateChangeEvents_);
-    }
-    if (auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr)) {
-        // application notification
-        while (not eventDataQueue.empty()) {
-            auto& evdata = eventDataQueue.front();
-            evdata.tls_info.ssl_sock_info = &evdata.ssl_info;
-            evdata.state_info.ext_info = &evdata.tls_info;
-            (*state_cb)(&trData_.base, evdata.state, &evdata.state_info);
-            eventDataQueue.pop();
-        }
-    }
-
-    // Handle stream GnuTLS -> SIP transport
-    decltype(rxPending_) rx;
-    {
-        std::lock_guard<std::mutex> l(rxMtx_);
-        rx = std::move(rxPending_);
-    }
-
-    for (auto it = rx.begin(); it != rx.end(); ++it) {
-        auto& pck = *it;
-        pj_pool_reset(rdata_.tp_info.pool);
-        pj_gettimeofday(&rdata_.pkt_info.timestamp);
-        rdata_.pkt_info.len = pck.size();
-        std::copy_n(pck.data(), pck.size(), rdata_.pkt_info.packet);
-        auto eaten = pjsip_tpmgr_receive_packet(trData_.base.tpmgr, &rdata_);
-        if (eaten != rdata_.pkt_info.len) {
-            // partial sip packet received
-            auto npck_it = std::next(it);
-            if (npck_it != rx.end()) {
-                // drop current packet, merge reminder with next one
-                auto& npck = *npck_it;
-                npck.insert(npck.begin(), pck.begin()+eaten, pck.end());
-            } else {
-                // erase eaten part, keep reminder
-                pck.erase(pck.begin(), pck.begin()+eaten);
-                {
-                    std::lock_guard<std::mutex> l(rxMtx_);
-                    rxPending_.splice(rxPending_.begin(), rx, it);
-                }
-                break;
-            }
-        }
-    }
-    putBuff(std::move(rx));
-    rxCv_.notify_all();
-
-    // Report status GnuTLS -> SIP transport
-    decltype(outputAckBuff_) ackBuf;
-    {
-        std::lock_guard<std::mutex> l(outputBuffMtx_);
-        ackBuf = std::move(outputAckBuff_);
-    }
-    for (const auto& pair: ackBuf) {
-        const auto& f = pair.first;
-        f.tdata_op_key->tdata = nullptr;
-        RING_DBG("status: %ld", pair.second);
-        if (f.tdata_op_key->callback)
-            f.tdata_op_key->callback(getTransportBase(), f.tdata_op_key->token,
-                                     pair.second);
-    }
-    cv_.notify_all();
-}
-
-bool
-SipsIceTransport::setup()
+SipsIceTransport::certGetCn(const pj_str_t* gen_name, pj_str_t* cn)
 {
-    RING_DBG("Starting GnuTLS thread");
-
-    // permit incoming packets
-    ice_->setOnRecv(comp_id_, [this](uint8_t* buf, size_t len) {
-            {
-                std::lock_guard<std::mutex> l(inputBuffMtx_);
-                tlsInputBuff_.emplace_back(buf, buf+len);
-                canRead_ = true;
-                RING_DBG("TLS(ice): rx %zuB", len);
-            }
-            cv_.notify_all();
-            return len;
-        });
-
-    if (is_server_) {
-        gnutls_key_generate(&cookie_key_, GNUTLS_COOKIE_KEY_SIZE);
-        state_ = TlsConnectionState::COOKIE;
-        return true;
-    }
+    pj_str_t CN_sign = {(char*)"CN=", 3};
+    char *p, *q;
 
-    return startTlsSession() == PJ_SUCCESS;
-}
+    std::memset(cn, 0, sizeof(*cn));
 
-void
-SipsIceTransport::loop()
-{
-    if (not ice_->isRunning()) {
-        shutdown();
+    p = pj_strstr(gen_name, &CN_sign);
+    if (!p)
         return;
-    }
-
-    if (state_ == TlsConnectionState::COOKIE) {
-        {
-            std::unique_lock<std::mutex> l(inputBuffMtx_);
-            if (tlsInputBuff_.empty()) {
-                cv_.wait(l, [&](){
-                    return state_ != TlsConnectionState::COOKIE or not tlsInputBuff_.empty();
-                });
-            }
-            if (state_ != TlsConnectionState::COOKIE)
-                return;
-            const auto& pck = tlsInputBuff_.front();
-            std::memset(&prestate_, 0, sizeof(prestate_));
-            int ret = gnutls_dtls_cookie_verify(&cookie_key_,
-                                                &trData_.base.key.rem_addr,
-                                                trData_.base.addr_len,
-                                                (char*)pck.data(), pck.size(),
-                                                &prestate_);
-            if (ret < 0) {
-                gnutls_dtls_cookie_send(&cookie_key_,
-                                        &trData_.base.key.rem_addr,
-                                        trData_.base.addr_len, &prestate_, this,
-                                        [](gnutls_transport_ptr_t t,
-                                           const void* d, size_t s) -> ssize_t {
-                    auto this_ = reinterpret_cast<SipsIceTransport*>(t);
-                    return this_->tlsSend(d, s);
-                });
-                tlsInputBuff_.pop_front();
-                if (tlsInputBuff_.empty())
-                    canRead_ = false;
-                return;
-            }
-        }
-        startTlsSession();
-    }
-
-    if (state_ == TlsConnectionState::HANDSHAKING) {
-        if (clock::now() - handshakeStart_ > param_.timeout) {
-            onHandshakeComplete(PJ_ETIMEDOUT);
-            return;
-        }
-        auto status = tryHandshake();
-        if (status != PJ_EPENDING)
-            onHandshakeComplete(status);
-    }
-
-    if (state_ == TlsConnectionState::ESTABLISHED) {
-        {
-            std::mutex flagsMtx_ {};
-            std::unique_lock<std::mutex> l(flagsMtx_);
-            cv_.wait(l, [&](){
-                return state_ != TlsConnectionState::ESTABLISHED or canRead_ or canWrite_;
-            });
-        }
-
-        if (state_ != TlsConnectionState::ESTABLISHED and not getTransportBase()->is_shutdown)
-            return;
-
-        decltype(rxPending_) rx;
-        while (canRead_ or gnutls_record_check_pending(session_)) {
-            if (rx.empty())
-                getBuff(rx, PJSIP_MAX_PKT_LEN);
-            auto& buf = rx.front();
-            const auto decrypted_size = gnutls_record_recv(session_, buf.data(), buf.size());
-
-            if (decrypted_size > 0/* || transport error */) {
-                buf.resize(decrypted_size);
-                {
-                    std::lock_guard<std::mutex> l(rxMtx_);
-                    rxPending_.splice(rxPending_.end(), rx, rx.begin());
-                }
-            } else if (decrypted_size == 0) {
-                /* EOF */
-                tlsThread_.stop();
-                break;
-            } else if (decrypted_size == GNUTLS_E_AGAIN or
-                       decrypted_size == GNUTLS_E_INTERRUPTED) {
-                break;
-            } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) {
-                /* Seems like we are renegotiating */
-
-                RING_DBG("rehandshake");
-                auto try_handshake_status = tryHandshake();
-
-                /* Not pending is either success or failed */
-                if (try_handshake_status != PJ_EPENDING) {
-                    if (!onHandshakeComplete(try_handshake_status)) {
-                        break;
-                    }
-                }
-
-                if (try_handshake_status != PJ_SUCCESS and
-                    try_handshake_status != PJ_EPENDING) {
-                    break;
-                }
-            } else if (!gnutls_error_is_fatal(decrypted_size)) {
-                /* non-fatal error, let's just continue */
-            } else {
-                shutdown();
-                break;
-            }
-        }
-        putBuff(std::move(rx));
-        flushOutputBuff();
-    }
-}
-
-void
-SipsIceTransport::clean()
-{
-    RING_DBG("Ending GnuTLS thread");
-
-    // Forbid GnuTLS <-> ICE IOs
-    ice_->setOnRecv(comp_id_, nullptr);
-    tlsInputBuff_.clear();
-    canRead_ = false;
-    canWrite_ = false;
-
-    {
-        // Reply all SIP transport send requests with ENOTCONN status error
-        std::unique_lock<std::mutex> l(outputBuffMtx_);
-
-        for (const auto& f : outputBuff_) {
-            f.tdata_op_key->tdata = nullptr;
-            if (f.tdata_op_key->callback)
-                outputAckBuff_.emplace_back(std::move(f), -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN)); // see handleEvents()
-        }
-        outputBuff_.clear();
-
-        // make sure that all callbacks are called before closing
-        cv_.wait(l, [&](){
-                return outputAckBuff_.empty();
-            });
-    }
-
-    // note: incoming packets (rxPending_) will be processed in destructor
-
-    if (cookie_key_.data) {
-        gnutls_free(cookie_key_.data);
-        cookie_key_.data = nullptr;
-        cookie_key_.size = 0;
-    }
-
-    closeTlsSession();
-}
-
-IpAddr
-SipsIceTransport::getLocalAddress() const
-{
-    return ice_->getLocalAddress(comp_id_);
-}
-
-IpAddr
-SipsIceTransport::getRemoteAddress() const
-{
-    return ice_->getRemoteAddress(comp_id_);
-}
-
-ssize_t
-SipsIceTransport::tlsSend(const void* d, size_t s)
-{
-    return ice_->send(comp_id_, (const uint8_t*)d, s);
-}
 
-ssize_t
-SipsIceTransport::tlsRecv(void* d , size_t s)
-{
-    std::lock_guard<std::mutex> l(inputBuffMtx_);
-    if (tlsInputBuff_.empty()) {
-        gnutls_transport_set_errno(session_, EAGAIN);
-        return -1;
-    }
-    const auto& front = tlsInputBuff_.front();
-    const auto n = std::min(front.size(), s);
-    std::copy_n(front.begin(), n, (uint8_t*)d);
-    tlsInputBuff_.pop_front();
-    if (tlsInputBuff_.empty())
-        canRead_ = false;
-    return n;
-}
-
-int
-SipsIceTransport::waitForTlsData(unsigned ms)
-{
-    std::unique_lock<std::mutex> l(inputBuffMtx_);
-    if (tlsInputBuff_.empty()) {
-        cv_.wait_for(l, std::chrono::milliseconds(ms), [&]() {
-            return state_ == TlsConnectionState::DISCONNECTED or not tlsInputBuff_.empty();
-        });
-    }
-    if (state_ == TlsConnectionState::DISCONNECTED) {
-        gnutls_transport_set_errno(session_, EINTR);
-        return -1;
-    }
-    return tlsInputBuff_.empty() ? 0 : tlsInputBuff_.front().size();
+    p += 3; /* shift pointer to value part */
+    pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr));
+    q = pj_strchr(cn, ',');
+    if (q)
+        cn->slen = q - p;
 }
 
 pj_status_t
@@ -966,251 +631,30 @@ SipsIceTransport::send(pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr,
                       addr_len==sizeof(pj_sockaddr_in6)),
                      PJ_EINVAL);
 
+    tdata->op_key.tdata = tdata;
     tdata->op_key.token = token;
     tdata->op_key.callback = callback;
-    if (state_ == TlsConnectionState::ESTABLISHED) {
-        decltype(txBuff_) tx;
-        size_t size = tdata->buf.cur - tdata->buf.start;
-        getBuff(tx, (uint8_t*)tdata->buf.start, (uint8_t*)tdata->buf.cur);
-        {
-            std::lock_guard<std::mutex> l(outputBuffMtx_);
-            txBuff_.splice(txBuff_.end(), std::move(tx));
-        }
-        tdata->op_key.tdata = nullptr;
-        if (tdata->op_key.callback)
-            tdata->op_key.callback(getTransportBase(), token, size);
-    } else {
-        std::lock_guard<std::mutex> l(outputBuffMtx_);
-        tdata->op_key.tdata = tdata;
-        outputBuff_.emplace_back(DelayedTxData{&tdata->op_key, {}});
-        if (tdata->msg && tdata->msg->type == PJSIP_REQUEST_MSG) {
-            auto& dtd = outputBuff_.back();
-            dtd.timeout = clock::now();
-            dtd.timeout += std::chrono::milliseconds(pjsip_cfg()->tsx.td);
-        }
-    }
-    canWrite_ = true;
-    cv_.notify_all();
-    return PJ_EPENDING;
-}
-
-pj_status_t
-SipsIceTransport::flushOutputBuff()
-{
-    ssize_t status = PJ_SUCCESS;
-
-    // send delayed data first
-    for (;;) {
-        bool fatal = true;
-        DelayedTxData f;
 
-        {
-            std::lock_guard<std::mutex> l(outputBuffMtx_);
-            if (outputBuff_.empty())
-                break;
-            f = std::move(outputBuff_.front());
-            outputBuff_.pop_front();
-        }
-
-        // Too late?
-        if (f.timeout != clock::time_point() && f.timeout < clock::now()) {
-            status = GNUTLS_E_TIMEDOUT;
-            fatal = false;
-        } else {
-            status = trySend(f.tdata_op_key);
-            fatal = status < 0;
-            if (fatal) {
-                if (gnutls_error_is_fatal(status))
-                    tlsThread_.stop();
-                else {
-                    // Failed but non-fatal. Retry later.
-                    std::lock_guard<std::mutex> l(outputBuffMtx_);
-                    outputBuff_.emplace_front(std::move(f));
-                }
-            }
-        }
-
-        f.tdata_op_key->tdata = nullptr;
-
-        if (f.tdata_op_key->callback) {
-            std::lock_guard<std::mutex> l(outputBuffMtx_);
-            outputAckBuff_.emplace_back(std::move(f), status > 0 ? status : -tls_status_from_err(status)); // see handleEvents()
-        }
-
-        if (fatal)
-            return -tls_status_from_err(status);
-    }
-
-    decltype(txBuff_) tx;
-    {
-        std::lock_guard<std::mutex> l(outputBuffMtx_);
-        tx = std::move(txBuff_);
-        canWrite_ = false;
-    }
-    for (auto it = tx.begin(); it != tx.end(); ++it) {
-        const auto& msg = *it;
-        const auto nwritten = gnutls_record_send(session_, msg.data(), msg.size());
-        if (nwritten <= 0) {
-            status = nwritten;
-            if (gnutls_error_is_fatal(status))
-                tlsThread_.stop();
-            {
-                std::lock_guard<std::mutex> l(outputBuffMtx_);
-                txBuff_.splice(txBuff_.begin(), tx, it, tx.end());
-                canWrite_ = true;
-            }
-            break;
-        }
-    }
-    putBuff(std::move(tx));
-    return status > 0 ? PJ_SUCCESS : -tls_status_from_err(status);
-}
-
-ssize_t
-SipsIceTransport::trySend(pjsip_tx_data_op_key *pck)
-{
-    const auto tdata = pck->tdata;
-    const size_t size = tdata->buf.cur - tdata->buf.start;
-    const size_t max_tx_sz = gnutls_dtls_get_data_mtu(session_);
-
-    size_t total_written = 0;
-    while (total_written < size) {
-        /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push
-         * callback to actually send the encrypted bytes. */
-        const auto tx_size = std::min(max_tx_sz, size - total_written);
-        const auto nwritten = gnutls_record_send(session_,
-                                                 tdata->buf.start + total_written,
-                                                 tx_size);
-        if (nwritten <= 0) {
-            /* Normally we would have to retry record_send but our internal
-             * state has not changed, so we have to ask for more data first.
-             * We will just try again later, although this should never happen.
-             */
-            return nwritten;
-        }
-
-        /* Good, some data was encrypted and written */
-        total_written += nwritten;
-    }
-    return total_written;
-}
-
-void
-SipsIceTransport::shutdown()
-{
-    RING_DBG("%s", __PRETTY_FUNCTION__);
-    state_ = TlsConnectionState::DISCONNECTED;
-    tlsThread_.stop();
-    cv_.notify_all();
-}
-
-void
-SipsIceTransport::getBuff(decltype(buffPool_)& l, const uint8_t* b, const uint8_t* e)
-{
-    std::lock_guard<std::mutex> lk(buffPoolMtx_);
-    if (buffPool_.empty())
-        l.emplace_back(b, e);
-    else {
-        l.splice(l.end(), buffPool_, buffPool_.begin());
-        auto& buf = l.back();
-        buf.resize(std::distance(b, e));
-        std::copy(b, e, buf.begin());
-    }
-}
-
-void
-SipsIceTransport::getBuff(decltype(buffPool_)& l, const size_t s)
-{
-    std::lock_guard<std::mutex> lk(buffPoolMtx_);
-    if (buffPool_.empty())
-        l.emplace_back(s);
-    else {
-        l.splice(l.end(), buffPool_, buffPool_.begin());
-        auto& buf = l.back();
-        buf.resize(s);
-    }
-}
-
-void
-SipsIceTransport::putBuff(decltype(buffPool_)&& l)
-{
-    std::lock_guard<std::mutex> lk(buffPoolMtx_);
-    buffPool_.splice(buffPool_.end(), l);
-}
-
-pj_status_t
-SipsIceTransport::tls_status_from_err(int err)
-{
-    pj_status_t status;
+    // Asynchronous send
+    const std::size_t size = tdata->buf.cur - tdata->buf.start;
+    auto ret = tls_->async_send(tdata->buf.start, size, [=](std::size_t bytes_sent){
+            if (bytes_sent == 0)
+                bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+            std::lock_guard<std::mutex> l(txAckQueueMutex_);
+            txAckQueue_.emplace_back(std::make_pair(tdata, bytes_sent));
+        });
 
-    switch (err) {
-    case GNUTLS_E_SUCCESS:
-        status = PJ_SUCCESS;
-        break;
-    case GNUTLS_E_MEMORY_ERROR:
-        status = PJ_ENOMEM;
-        break;
-    case GNUTLS_E_LARGE_PACKET:
-        status = PJ_ETOOBIG;
-        break;
-    case GNUTLS_E_NO_CERTIFICATE_FOUND:
-        status = PJ_ENOTFOUND;
-        break;
-    case GNUTLS_E_SESSION_EOF:
-        status = PJ_EEOF;
-        break;
-    case GNUTLS_E_HANDSHAKE_TOO_LARGE:
-        status = PJ_ETOOBIG;
-        break;
-    case GNUTLS_E_EXPIRED:
-        status = PJ_EGONE;
-        break;
-    case GNUTLS_E_TIMEDOUT:
-        status = PJ_ETIMEDOUT;
-        break;
-    case GNUTLS_E_PREMATURE_TERMINATION:
-        status = PJ_ECANCELLED;
-        break;
-    case GNUTLS_E_INTERNAL_ERROR:
-    case GNUTLS_E_UNIMPLEMENTED_FEATURE:
-        status = PJ_EBUG;
-        break;
-    case GNUTLS_E_AGAIN:
-    case GNUTLS_E_INTERRUPTED:
-    case GNUTLS_E_REHANDSHAKE:
-        status = PJ_EPENDING;
-        break;
-    case GNUTLS_E_TOO_MANY_EMPTY_PACKETS:
-    case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS:
-    case GNUTLS_E_RECORD_LIMIT_REACHED:
-        status = PJ_ETOOMANY;
-        break;
-    case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
-    case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
-    case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE:
-    case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE:
-    case GNUTLS_E_X509_UNSUPPORTED_EXTENSION:
-    case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION:
-        status = PJ_ENOTSUP;
-        break;
-    case GNUTLS_E_INVALID_SESSION:
-    case GNUTLS_E_INVALID_REQUEST:
-    case GNUTLS_E_INVALID_PASSWORD:
-    case GNUTLS_E_ILLEGAL_PARAMETER:
-    case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION:
-    case GNUTLS_E_UNEXPECTED_PACKET:
-    case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
-    case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
-    case GNUTLS_E_UNWANTED_ALGORITHM:
-    case GNUTLS_E_USER_ERROR:
-        status = PJ_EINVAL;
-        break;
-    default:
-        status = PJ_EUNKNOWN;
-        break;
+    // Shutdown on fatal errors
+    if (gnutls_error_is_fatal(ret)) {
+        tdata->op_key.tdata = nullptr;
+        if (callback)
+            callback(&trData_.base, token, -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN));
+        RING_ERR("[TLS] send failed: %s", gnutls_strerror(ret));
+        tls_->shutdown();
+        return tls_status_from_err(ret);
     }
 
-    return status;
+    return PJ_EPENDING;
 }
 
 }} // namespace ring::tls
diff --git a/src/ringdht/sips_transport_ice.h b/src/ringdht/sips_transport_ice.h
index 365d89735151bd924f148e35675b22dc2470bc74..27b1d65a513a3a6051300e4300fd72b829d89b07 100644
--- a/src/ringdht/sips_transport_ice.h
+++ b/src/ringdht/sips_transport_ice.h
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2004-2015 Savoir-faire Linux Inc.
+ *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
  *
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -20,10 +21,9 @@
 
 #pragma once
 
+#include "security/tls_session.h"
 #include "ip_utils.h"
-#include "threadloop.h"
-
-#include <opendht/crypto.h>
+#include "noncopyable.h"
 
 #include <pjsip.h>
 #include <pj/pool.h>
@@ -36,11 +36,10 @@
 #include <functional>
 #include <memory>
 #include <mutex>
-#include <condition_variable>
 #include <chrono>
-#include <future>
 #include <queue>
 #include <utility>
+#include <vector>
 
 namespace ring {
 class IceTransport;
@@ -48,23 +47,11 @@ class IceTransport;
 
 namespace ring { namespace tls {
 
-enum class TlsConnectionState {
-    DISCONNECTED,
-    COOKIE,
-    HANDSHAKING,
-    ESTABLISHED
-};
-
-struct TlsParams {
-    std::string ca_list;
-    dht::crypto::Identity id;
-    std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dh_params;
-    std::chrono::steady_clock::duration timeout;
-    std::function<pj_status_t(unsigned status,
-                              const gnutls_datum_t* cert_list,
-                              unsigned cert_list_size)> cert_check;
-};
-
+/**
+ * SipsIceTransport
+ *
+ * Implements TLS transport as an pjsip_transport
+ */
 struct SipsIceTransport
 {
     using clock = std::chrono::steady_clock;
@@ -81,57 +68,36 @@ struct SipsIceTransport
 
     void shutdown();
 
-    IpAddr getLocalAddress() const;
-    IpAddr getRemoteAddress() const;
-
-    std::shared_ptr<IceTransport> getIceTransport() const {
-        return ice_;
-    }
+    std::shared_ptr<IceTransport> getIceTransport() const { return ice_; }
+    pjsip_transport* getTransportBase() { return &trData_.base; }
 
-    pjsip_transport* getTransportBase() {
-        return &trData_.base;
-    }
+    IpAddr getLocalAddress() const { return local_; }
+    IpAddr getRemoteAddress() const { return remote_; }
 
 private:
-    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
-    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> rxPool_;
-
-    using DelayedTxData = struct {
-        pjsip_tx_data_op_key *tdata_op_key;
-        clock::time_point timeout;
-    };
+    NON_COPYABLE(SipsIceTransport);
 
-    TransportData trData_; // uplink to this (used in C callbacks)
     const std::shared_ptr<IceTransport> ice_;
     const int comp_id_;
-
-    TlsParams param_;
-
-    bool is_server_ {false};
-    //bool has_pending_connect_ {false};
+    const std::function<int(unsigned, const gnutls_datum_t*, unsigned)> certCheck_;
     IpAddr local_ {};
     IpAddr remote_ {};
 
-    ThreadLoop tlsThread_;
-    std::condition_variable_any cv_;
-    std::atomic<bool> canRead_ {false};
-    std::atomic<bool> canWrite_ {false};
+    // PJSIP transport backend
+
+    TransportData trData_; // uplink to "this" (used by PJSIP called C-callbacks)
 
-    // TODO
-    pj_status_t verifyStatus_ {};
-    int last_err_;
+    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)*> pool_;
+    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)*> rxPool_;
+
+    pjsip_rx_data rdata_;
 
     pj_ssl_cert_info localCertInfo_;
     pj_ssl_cert_info remoteCertInfo_;
 
-    std::atomic<TlsConnectionState> state_ {TlsConnectionState::DISCONNECTED};
-    clock::time_point handshakeStart_;
+    pj_status_t verifyStatus_ {PJ_EUNKNOWN};
 
-    gnutls_session_t session_ {nullptr};
-    std::unique_ptr<gnutls_certificate_credentials_st, decltype(gnutls_certificate_free_credentials)&> xcred_;
-    gnutls_priority_t priority_cache_ {nullptr};
-    gnutls_datum_t cookie_key_ {nullptr, 0};
-    gnutls_dtls_prestate_st prestate_;
+    // TlsSession backend
 
     struct ChangeStateEventData {
         pj_ssl_sock_info ssl_info;
@@ -140,60 +106,29 @@ private:
         decltype(PJSIP_TP_STATE_DISCONNECTED) state;
     };
 
-    std::mutex stateChangeEventsMutex_;
-    std::queue<ChangeStateEventData> stateChangeEvents_;
+    std::unique_ptr<TlsSession> tls_;
+    std::atomic_bool tlsConnected_ {false}; // set by updateTransportState
 
-    /**
-     * To be called on a regular basis to receive packets
-     */
-    void handleEvents();
-
-    // ThreadLoop
-    bool setup();
-    void loop();
-    void clean();
-
-    // SIP transport <-> GnuTLS
-    pj_status_t send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr,
-                     int addr_len, void* token,
-                     pjsip_transport_callback callback);
-    ssize_t trySend(pjsip_tx_data_op_key* tdata);
-    pj_status_t flushOutputBuff();
-    std::list<DelayedTxData> outputBuff_;
-    std::list<std::pair<DelayedTxData, ssize_t>> outputAckBuff_; // acknowledged outputBuff_
-    std::list<std::vector<uint8_t>> txBuff_;
-    std::mutex outputBuffMtx_;
+    std::mutex stateChangeEventsMutex_ {};
+    std::list<ChangeStateEventData> stateChangeEvents_ {};
 
     std::mutex rxMtx_;
-    std::condition_variable_any rxCv_;
     std::list<std::vector<uint8_t>> rxPending_;
-    pjsip_rx_data rdata_;
 
-    // data buffer pool
-    std::list<std::vector<uint8_t>> buffPool_;
-    std::mutex buffPoolMtx_;
-    void getBuff(decltype(buffPool_)& l, const uint8_t* b, const uint8_t* e);
-    void getBuff(decltype(buffPool_)& l, const size_t s);
-    void putBuff(decltype(buffPool_)&& l);
-
-    // GnuTLS <-> ICE
-    ssize_t tlsSend(const void*, size_t);
-    ssize_t tlsRecv(void* d , size_t s);
-    int waitForTlsData(unsigned ms);
-    std::mutex inputBuffMtx_;
-    std::list<std::vector<uint8_t>> tlsInputBuff_;
-
-    pj_status_t startTlsSession();
-    void closeTlsSession();
-
-    pj_status_t tryHandshake();
-    void certGetCn(const pj_str_t* gen_name, pj_str_t* cn);
-    void certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci, const gnutls_datum_t* cert, size_t crt_raw_num);
-    void certUpdate();
-    bool onHandshakeComplete(pj_status_t status);
-    int verifyCertificate();
-    void getInfo(pj_ssl_sock_info* info);
-    static pj_status_t tls_status_from_err(int err);
+    std::mutex txAckQueueMutex_;
+    std::list<std::pair<pjsip_tx_data*, ssize_t>> txAckQueue_;
+
+    pj_status_t send(pjsip_tx_data*, const pj_sockaddr_t*, int, void*, pjsip_transport_callback);
+    void handleEvents();
+    void pushChangeStateEvent(ChangeStateEventData&&);
+    void updateTransportState(pjsip_transport_state);
+    void certGetInfo(pj_pool_t*, pj_ssl_cert_info*, const gnutls_datum_t*, size_t);
+    void certGetCn(const pj_str_t*, pj_str_t*);
+    void getInfo(pj_ssl_sock_info*, bool);
+    void onTlsStateChange(TlsSessionState);
+    void onRxData(std::vector<uint8_t>&&);
+    void onCertificatesUpdate(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int);
+    int verifyCertificate(gnutls_session_t);
 };
 
 }} // namespace ring::tls
diff --git a/src/security/Makefile.am b/src/security/Makefile.am
index 44f1b26bae7613135e6ba9c5a4b96e3e5506fad7..8321a993b0313a2f1bf8b85dc5964ebfc54e5829 100644
--- a/src/security/Makefile.am
+++ b/src/security/Makefile.am
@@ -4,7 +4,9 @@ noinst_LTLIBRARIES = libsecurity.la
 libsecurity_la_CXXFLAGS = @CXXFLAGS@ -I$(top_srcdir)/src
 
 libsecurity_la_SOURCES = \
-        tlsvalidator.cpp \
-        tlsvalidator.h \
-        certstore.cpp \
-        certstore.h
+		tls_session.cpp \
+		tls_session.h \
+		tlsvalidator.cpp \
+		tlsvalidator.h \
+		certstore.cpp \
+		certstore.h
diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b6f0d5fa73917ff70d0d728ecc9861410cb6f174
--- /dev/null
+++ b/src/security/tls_session.cpp
@@ -0,0 +1,662 @@
+/*
+ *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include <ip_utils.h>       // DO NOT CHANGE ORDER OF THIS INCLUDE
+#include <opendht/crypto.h> // OR MINGWIN FAILS TO BUILD
+
+#include "tls_session.h"
+
+#include "ice_socket.h"
+#include "ice_transport.h"
+#include "logger.h"
+#include "noncopyable.h"
+#include "intrin.h"
+
+#include <gnutls/dtls.h>
+#include <gnutls/abstract.h>
+
+#include <algorithm>
+#include <cstring> // std::memset
+
+namespace ring { namespace tls {
+
+static constexpr int DTLS_MTU {1400}; // limit for networks like ADSL
+static constexpr const char* TLS_PRIORITY_STRING {"SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:%SERVER_PRECEDENCE"};
+static constexpr ssize_t FLOOD_THRESHOLD {4*1024};
+static constexpr auto FLOOD_PAUSE = std::chrono::milliseconds(100); // Time to wait after an invalid cookie packet (anti flood attack)
+static constexpr std::size_t INPUT_MAX_SIZE {1000}; // Maximum packet to store before dropping (pkt size = DTLS_MTU)
+
+class TlsSession::TlsCertificateCredendials
+{
+    using T = gnutls_certificate_credentials_t;
+public:
+    TlsCertificateCredendials() {
+        int ret = gnutls_certificate_allocate_credentials(&creds_);
+        if (ret < 0) {
+            RING_ERR("gnutls_certificate_allocate_credentials() failed with ret=%d", ret);
+            throw std::bad_alloc();
+        }
+    }
+
+    ~TlsCertificateCredendials() {
+        gnutls_certificate_free_credentials(creds_);
+    }
+
+    operator T() { return creds_; }
+
+private:
+    NON_COPYABLE(TlsCertificateCredendials);
+    T creds_;
+};
+
+TlsSession::TlsSession(std::shared_ptr<IceTransport> ice, int ice_comp_id,
+                       const TlsParams& params, const TlsSessionCallbacks& cbs)
+    : socket_(new IceSocket(ice, ice_comp_id))
+    , isServer_(not ice->isInitiator())
+    , params_(params)
+    , callbacks_(cbs)
+    , xcred_(nullptr)
+    , thread_([this] { return setup(); },
+              [this] { process(); },
+              [this] { cleanup(); })
+{
+    // Setup TLS algorithms priority list
+    auto ret = gnutls_priority_init(&priority_cache_, TLS_PRIORITY_STRING, nullptr);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("[TLS] priority setup failed: %s", gnutls_strerror(ret));
+        throw std::runtime_error("TlsSession");
+    }
+
+    socket_->setOnRecv([this](uint8_t* buf, size_t len) {
+            std::lock_guard<std::mutex> lk {ioMutex_};
+            if (rxQueue_.size() == INPUT_MAX_SIZE) {
+                rxQueue_.pop_front(); // drop oldest packet if input buffer is full
+                ++stRxRawPacketDropCnt_;
+            }
+            rxQueue_.emplace_back(buf, buf+len);
+            ++stRxRawPacketCnt_;
+            stRxRawBytesCnt_ += len;
+            ioCv_.notify_one();
+            return len;
+        });
+
+    // Run FSM into dedicated thread
+    thread_.start();
+}
+
+TlsSession::~TlsSession()
+{
+    shutdown();
+    thread_.join();
+
+    socket_->setOnRecv(nullptr);
+
+    if (priority_cache_)
+        gnutls_priority_deinit(priority_cache_);
+}
+
+const char*
+TlsSession::typeName() const
+{
+    return isServer_ ? "server" : "client";
+}
+
+void
+TlsSession::dump_io_stats() const
+{
+    RING_WARN("[TLS] RxRawPckt=%zu (%zu bytes)", stRxRawPacketCnt_, stRxRawBytesCnt_);
+}
+
+TlsSessionState
+TlsSession::setupClient()
+{
+    auto ret = gnutls_init(&session_, GNUTLS_CLIENT | GNUTLS_DATAGRAM);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("[TLS] session init failed: %s", gnutls_strerror(ret));
+        return TlsSessionState::SHUTDOWN;
+    }
+
+    if (not commonSessionInit()) {
+        return TlsSessionState::SHUTDOWN;
+    }
+
+    return TlsSessionState::HANDSHAKE;
+}
+
+TlsSessionState
+TlsSession::setupServer()
+{
+    gnutls_key_generate(&cookie_key_, GNUTLS_COOKIE_KEY_SIZE);
+    return TlsSessionState::COOKIE;
+}
+
+void
+TlsSession::initCredentials()
+{
+    int ret;
+
+    // credentials for handshaking and transmission
+    xcred_.reset(new TlsCertificateCredendials());
+
+    if (callbacks_.verifyCertificate)
+        gnutls_certificate_set_verify_function(*xcred_, [](gnutls_session_t session) -> int {
+                auto this_ = reinterpret_cast<TlsSession*>(gnutls_session_get_ptr(session));
+                return this_->callbacks_.verifyCertificate(session);
+            });
+
+    // Load user-given CA list
+    if (not params_.ca_list.empty()) {
+        // Try PEM format first
+        ret = gnutls_certificate_set_x509_trust_file(*xcred_, params_.ca_list.c_str(),
+                                                     GNUTLS_X509_FMT_PEM);
+
+        // Then DER format
+        if (ret < 0)
+            ret = gnutls_certificate_set_x509_trust_file(*xcred_, params_.ca_list.c_str(),
+                                                         GNUTLS_X509_FMT_DER);
+        if (ret < 0)
+            throw std::runtime_error("can't load CA " + params_.ca_list + ": "
+                                     + std::string(gnutls_strerror(ret)));
+
+        RING_DBG("[TLS] CA list %s loadev", params_.ca_list.c_str());
+    }
+
+    // Load user-given identity (key and passwd)
+    if (params_.cert) {
+        ret = gnutls_certificate_set_x509_key(*xcred_, &params_.cert->cert, 1,
+                                              params_.cert_key->x509_key);
+        if (ret < 0)
+            throw std::runtime_error("can't load certificate: "
+                                     + std::string(gnutls_strerror(ret)));
+
+        RING_DBG("[TLS] User identity loaded");
+    }
+
+    // Setup DH-params (server only, may block on dh_params.get())
+    if (isServer_) {
+        if (auto& dh_params = params_.dh_params.get())
+            gnutls_certificate_set_dh_params(*xcred_, dh_params.get());
+        else
+            RING_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop?
+    }
+}
+
+bool
+TlsSession::commonSessionInit()
+{
+    int ret;
+
+    ret = gnutls_priority_set(session_, priority_cache_);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("[TLS] session TLS priority set failed: %s", gnutls_strerror(ret));
+        return false;
+    }
+
+    ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("[TLS] session credential set failed: %s", gnutls_strerror(ret));
+        return false;
+    }
+
+    // Stuff for transport callbacks
+    gnutls_session_set_ptr(session_, this);
+    gnutls_transport_set_ptr(session_, this);
+    gnutls_dtls_set_mtu(session_, DTLS_MTU);
+
+    // TODO: minimize user timeout
+    auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(params_.timeout).count();
+    gnutls_dtls_set_timeouts(session_, 1000, timeout);
+
+    gnutls_transport_set_vec_push_function(session_,
+                                           [](gnutls_transport_ptr_t t, const giovec_t* iov,
+                                              int iovcnt) -> ssize_t {
+                                               auto this_ = reinterpret_cast<TlsSession*>(t);
+                                               return this_->sendRawVec(iov, iovcnt);
+                                           });
+    gnutls_transport_set_pull_function(session_,
+                                       [](gnutls_transport_ptr_t t, void* d, size_t s) -> ssize_t {
+                                           auto this_ = reinterpret_cast<TlsSession*>(t);
+                                           return this_->recvRaw(d, s);
+                                       });
+    gnutls_transport_set_pull_timeout_function(session_,
+                                               [](gnutls_transport_ptr_t t, unsigned ms) -> int {
+                                                   auto this_ = reinterpret_cast<TlsSession*>(t);
+                                                   return this_->waitForRawData(ms);
+                                               });
+
+    return true;
+}
+
+// Called by anyone to stop the connection and the FSM thread
+void
+TlsSession::shutdown()
+{
+    state_ = TlsSessionState::SHUTDOWN;
+    ioCv_.notify_one(); // unblock waiting FSM
+}
+
+const char*
+TlsSession::getCurrentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const
+{
+    auto cipher = gnutls_cipher_get(session_);
+    gnutls_cipher_algorithm_t lookup;
+
+    // Loop on ciphers suite until our cipher is found
+    for (std::size_t i=0; ; ++i) {
+        const char* const suite = gnutls_cipher_suite_info(i, cs_id.data(), nullptr, &lookup, nullptr, nullptr);
+        if (lookup == cipher)
+            return suite;
+    }
+
+    return {};
+}
+
+// Called by application to send data to encrypt.
+ssize_t
+TlsSession::async_send(void* data, std::size_t size, TxDataCompleteFunc on_send_complete)
+{
+    if (state_ == TlsSessionState::SHUTDOWN)
+        return GNUTLS_E_INVALID_SESSION;
+    std::lock_guard<std::mutex> lk {ioMutex_};
+    txQueue_.emplace_back(TxData {data, size, on_send_complete});
+    ioCv_.notify_one();
+    return GNUTLS_E_SUCCESS;
+}
+
+ssize_t
+TlsSession::send(const TxData& tx_data)
+{
+    std::size_t max_tx_sz = gnutls_dtls_get_data_mtu(session_);
+    std::size_t tx_size = tx_data.size;
+    auto ptr = static_cast<uint8_t*>(tx_data.ptr);
+
+    // Split user data into MTU-suitable chunck
+    size_t total_written = 0;
+    while (total_written < tx_size) {
+        auto chunck_sz = std::min(max_tx_sz, tx_size - total_written);
+        auto nwritten = gnutls_record_send(session_, ptr + total_written, chunck_sz);
+        if (nwritten <= 0) {
+            /* Normally we would have to retry record_send but our internal
+             * state has not changed, so we have to ask for more data first.
+             * We will just try again later, although this should never happen.
+             */
+            RING_WARN("[TLS] send failed (only %zu bytes sent): %s", total_written,
+                      gnutls_strerror(nwritten));
+            return nwritten;
+        }
+
+        total_written += nwritten;
+    }
+    return total_written;
+}
+
+// Called by GNUTLS to send encrypted packet to low-level transport.
+// Should return a positive number indicating the bytes sent, and -1 on error.
+ssize_t
+TlsSession::sendRaw(const void* buf, size_t size)
+{
+    auto ret = socket_->send(reinterpret_cast<const unsigned char*>(buf), size);
+    if (ret > 0) {
+        // log only on success
+        ++stTxRawPacketCnt_;
+        stTxRawBytesCnt_ += size;
+    }
+    return ret;
+}
+
+// Called by GNUTLS to send encrypted packet to low-level transport.
+// Should return a positive number indicating the bytes sent, and -1 on error.
+ssize_t
+TlsSession::sendRawVec(const giovec_t* iov, int iovcnt)
+{
+    ssize_t sent = 0;
+    for (int i=0; i<iovcnt; ++i) {
+        const giovec_t& dat = iov[i];
+        ssize_t ret = sendRaw(dat.iov_base, dat.iov_len);
+        if (ret < 0)
+            return ret;
+        sent += ret;
+    }
+    return sent;
+}
+
+// Called by GNUTLS to receive encrypted packet from low-level transport.
+// Should return 0 on connection termination,
+// a positive number indicating the number of bytes received,
+// and -1 on error.
+ssize_t
+TlsSession::recvRaw(void* buf, size_t size)
+{
+    std::lock_guard<std::mutex> lk {ioMutex_};
+    if (rxQueue_.empty()) {
+        gnutls_transport_set_errno(session_, EAGAIN);
+        return -1;
+    }
+
+    const auto& pkt = rxQueue_.front();
+    const std::size_t count = std::min(pkt.size(), size);
+    std::copy_n(pkt.begin(), count, reinterpret_cast<uint8_t*>(buf));
+    rxQueue_.pop_front();
+    return count;
+}
+
+// Called by GNUTLS to wait for encrypted packet from low-level transport.
+// 'timeout' is in milliseconds.
+// Should return 0 on connection termination,
+// a positive number indicating the number of bytes received,
+// and -1 on error.
+int
+TlsSession::waitForRawData(unsigned timeout)
+{
+    std::unique_lock<std::mutex> lk {ioMutex_};
+    if (not ioCv_.wait_for(lk, std::chrono::milliseconds(timeout),
+                           [this]{ return !rxQueue_.empty() or state_ == TlsSessionState::SHUTDOWN; }))
+        return 0;
+
+    // shutdown?
+    if (state_ == TlsSessionState::SHUTDOWN) {
+        gnutls_transport_set_errno(session_, EINTR);
+        return -1;
+    }
+
+    return rxQueue_.front().size();
+}
+
+bool
+TlsSession::setup()
+{
+    // Setup FSM
+    fsmHandlers_[TlsSessionState::SETUP] = [this](TlsSessionState s){ return handleStateSetup(s); };
+    fsmHandlers_[TlsSessionState::COOKIE] = [this](TlsSessionState s){ return handleStateCookie(s); };
+    fsmHandlers_[TlsSessionState::HANDSHAKE] = [this](TlsSessionState s){ return handleStateHandshake(s); };
+    fsmHandlers_[TlsSessionState::ESTABLISHED] = [this](TlsSessionState s){ return handleStateEstablished(s); };
+    fsmHandlers_[TlsSessionState::SHUTDOWN] = [this](TlsSessionState s){ return handleStateShutdown(s); };
+
+    return true;
+}
+
+void
+TlsSession::cleanup()
+{
+    state_ = TlsSessionState::SHUTDOWN; // be sure to block any user operations
+
+    // Flush pending application send requests with a 0 bytes-sent result
+    for (auto& txdata : txQueue_) {
+        if (txdata.onComplete)
+            txdata.onComplete(0);
+    }
+
+    if (session_) {
+        // DTLS: not use GNUTLS_SHUT_RDWR to not wait for a peer answer
+        gnutls_bye(session_, GNUTLS_SHUT_WR);
+        gnutls_deinit(session_);
+        session_ = nullptr;
+    }
+
+    if (cookie_key_.data)
+        gnutls_free(cookie_key_.data);
+}
+
+TlsSessionState
+TlsSession::handleStateSetup(UNUSED TlsSessionState state)
+{
+    RING_DBG("[TLS] Start %s DTLS session", typeName());
+
+    try {
+        initCredentials();
+    } catch (const std::exception& e) {
+        RING_ERR("[TLS] credential init failed: %s", e.what());
+        return TlsSessionState::SHUTDOWN;
+    }
+
+    if (isServer_)
+        return setupServer();
+    else
+        return setupClient();
+}
+
+TlsSessionState
+TlsSession::handleStateCookie(TlsSessionState state)
+{
+    RING_DBG("[TLS] SYN cookie");
+
+    std::size_t count;
+    {
+        // block until rx packet or shutdown
+        std::unique_lock<std::mutex> lk {ioMutex_};
+        ioCv_.wait(lk, [this]{ return !rxQueue_.empty() or state_ == TlsSessionState::SHUTDOWN; });
+        if (rxQueue_.empty())
+            return TlsSessionState::SHUTDOWN;
+        count = rxQueue_.front().size();
+    }
+
+    // Total bytes rx during cookie checking (see flood protection below)
+    cookie_count_ += count;
+
+    int ret;
+
+    // Peek and verify front packet
+    {
+        std::lock_guard<std::mutex> lk {ioMutex_};
+        auto& pkt = rxQueue_.front();
+        std::memset(&prestate_, 0, sizeof(prestate_));
+        ret = gnutls_dtls_cookie_verify(&cookie_key_, nullptr, 0,
+                                        pkt.data(), pkt.size(), &prestate_);
+    }
+
+    if (ret < 0) {
+        gnutls_dtls_cookie_send(&cookie_key_, nullptr, 0, &prestate_,
+                                this,
+                                [](gnutls_transport_ptr_t t, const void* d,
+                                   size_t s) -> ssize_t {
+                                    auto this_ = reinterpret_cast<TlsSession*>(t);
+                                    return this_->sendRaw(d, s);
+                                });
+
+        // Drop front packet
+        {
+            std::lock_guard<std::mutex> lk {ioMutex_};
+            rxQueue_.pop_front();
+        }
+
+        // Cookie may be sent on multiple network packets
+        // So we retry until we get a valid cookie.
+        // To protect against a flood attack we delay each retry after FLOOD_THRESHOLD rx bytes.
+        if (cookie_count_ >= FLOOD_THRESHOLD) {
+            RING_WARN("[TLS] flood threshold reach (retry in %zds)",
+                      std::chrono::duration_cast<std::chrono::seconds>(FLOOD_PAUSE).count());
+            dump_io_stats();
+            std::this_thread::sleep_for(FLOOD_PAUSE); // flood attack protection
+        }
+        return state;
+    }
+
+    RING_DBG("[TLS] cookie ok");
+
+    ret = gnutls_init(&session_, GNUTLS_SERVER | GNUTLS_DATAGRAM);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("[TLS] session init failed: %s", gnutls_strerror(ret));
+        return TlsSessionState::SHUTDOWN;
+    }
+
+    gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE);
+    gnutls_dtls_prestate_set(session_, &prestate_);
+
+    if (not commonSessionInit())
+        return TlsSessionState::SHUTDOWN;
+
+    return TlsSessionState::HANDSHAKE;
+}
+
+TlsSessionState
+TlsSession::handleStateHandshake(TlsSessionState state)
+{
+    RING_DBG("[TLS] handshake");
+
+    auto ret = gnutls_handshake(session_);
+
+    if (ret == GNUTLS_E_SUCCESS) {
+        auto desc = gnutls_session_get_desc(session_);
+        RING_WARN("[TLS] Session established: %s", desc);
+        gnutls_free(desc);
+
+        // Aware about certificates updates
+        if (callbacks_.onCertificatesUpdate) {
+            unsigned int remote_count;
+            auto local = gnutls_certificate_get_ours(session_);
+            auto remote = gnutls_certificate_get_peers(session_, &remote_count);
+            callbacks_.onCertificatesUpdate(local, remote, remote_count);
+        }
+
+        return TlsSessionState::ESTABLISHED;
+    }
+
+    if (gnutls_error_is_fatal(ret)) {
+        RING_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret));
+        dump_io_stats();
+        return TlsSessionState::SHUTDOWN;
+    }
+
+    // TODO: handle GNUTLS_E_LARGE_PACKET (MTU must be lowered)
+    return state;
+}
+
+TlsSessionState
+TlsSession::handleStateEstablished(TlsSessionState state)
+{
+    // block until rx/tx packet or state change
+    std::unique_lock<std::mutex> lk {ioMutex_};
+    ioCv_.wait(lk, [this]{ return !txQueue_.empty() or !rxQueue_.empty() or state_ != TlsSessionState::ESTABLISHED; });
+    state = state_.load();
+    if (state != TlsSessionState::ESTABLISHED)
+        return state;
+
+    // Handle TX data from application
+    if (not txQueue_.empty()) {
+        decltype(txQueue_) tx_queue = std::move(txQueue_);
+        txQueue_.clear();
+        lk.unlock();
+        for (const auto& txdata : tx_queue) {
+            while (state_ == TlsSessionState::ESTABLISHED) {
+                auto bytes_sent = send(txdata);
+                auto fatal = gnutls_error_is_fatal(bytes_sent);
+                if (bytes_sent < 0 and !fatal)
+                    continue;
+                if (txdata.onComplete)
+                    txdata.onComplete(bytes_sent);
+                if (fatal)
+                    return TlsSessionState::SHUTDOWN;
+                break;
+            }
+        }
+        lk.lock();
+    }
+
+    // Handle RX data from network
+    if (!rxQueue_.empty()) {
+        std::vector<uint8_t> buf(8*1024);
+        unsigned char sequence[8];
+
+        lk.unlock();
+        auto ret = gnutls_record_recv_seq(session_, buf.data(), buf.size(), sequence);
+        if (ret > 0) {
+            buf.resize(ret);
+            // TODO: handle sequence re-order
+            if (callbacks_.onRxData)
+                callbacks_.onRxData(std::move(buf));
+            return state;
+        }
+
+        if (ret == 0) {
+            RING_DBG("[TLS] eof");
+            return TlsSessionState::SHUTDOWN;
+        }
+
+        if (ret == GNUTLS_E_REHANDSHAKE) {
+            RING_DBG("[TLS] re-handshake");
+            return TlsSessionState::HANDSHAKE;
+        }
+
+        if (gnutls_error_is_fatal(ret)) {
+            RING_ERR("[TLS] fatal error in recv: %s", gnutls_strerror(ret));
+            return TlsSessionState::SHUTDOWN;
+        }
+
+        // non-fatal error... let's continue
+        lk.lock();
+    }
+
+    return state;
+}
+
+TlsSessionState
+TlsSession::handleStateShutdown(TlsSessionState state)
+{
+    RING_DBG("[TLS] shutdown");
+
+    // Stop ourself
+    thread_.stop();
+    return state;
+}
+
+void
+TlsSession::process()
+{
+    auto old_state = state_.load();
+    auto new_state = fsmHandlers_[old_state](old_state);
+
+    // update state_ with taking care for external state change
+    if (not std::atomic_compare_exchange_strong(&state_, &old_state, new_state))
+        new_state = state_;
+
+    if (old_state != new_state and callbacks_.onStateChange)
+        callbacks_.onStateChange(new_state);
+}
+
+
+TlsParams::DhParams
+newDhParams()
+{
+    using clock = std::chrono::high_resolution_clock;
+
+    auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL);
+    RING_DBG("Generating DH params with %u bits", bits);
+    auto start = clock::now();
+
+    gnutls_dh_params_t new_params_;
+    int ret = gnutls_dh_params_init(&new_params_);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("Error initializing DH params: %s", gnutls_strerror(ret));
+        return {nullptr, gnutls_dh_params_deinit};
+    }
+
+    ret = gnutls_dh_params_generate2(new_params_, bits);
+    if (ret != GNUTLS_E_SUCCESS) {
+        RING_ERR("Error generating DH params: %s", gnutls_strerror(ret));
+        return {nullptr, gnutls_dh_params_deinit};
+    }
+
+    std::chrono::duration<double> time_span = clock::now() - start;
+    RING_DBG("Generated DH params with %u bits in %lfs", bits, time_span.count());
+    return {new_params_, gnutls_dh_params_deinit};
+}
+
+}} // namespace ring::tls
diff --git a/src/security/tls_session.h b/src/security/tls_session.h
new file mode 100644
index 0000000000000000000000000000000000000000..5bd2b5b7171bef4e785434455f7f102b71755ea5
--- /dev/null
+++ b/src/security/tls_session.h
@@ -0,0 +1,198 @@
+/*
+ *  Copyright (C) 2016 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "threadloop.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/dtls.h>
+#include <gnutls/abstract.h>
+
+#include <string>
+#include <list>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+#include <future>
+#include <utility>
+#include <vector>
+#include <map>
+
+namespace ring {
+class IceTransport;
+class IceSocket;
+} // namespace ring
+
+namespace dht { namespace crypto {
+class Certificate;
+class PrivateKey;
+}} // namespace dht::crypto
+
+namespace ring { namespace tls {
+
+enum class TlsSessionState {
+    SETUP,
+    COOKIE, // server only
+    HANDSHAKE,
+    ESTABLISHED,
+    SHUTDOWN
+};
+
+struct TlsParams {
+    using DhParams = std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>;
+
+    // User CA list for session credentials
+    std::string ca_list;
+
+    // User identity for credential
+    std::shared_ptr<dht::crypto::Certificate> cert;
+    std::shared_ptr<dht::crypto::PrivateKey> cert_key;
+
+    // Diffie-Hellman computed by gnutls_dh_params_init/gnutls_dh_params_generateX
+    std::shared_future<DhParams> dh_params;
+
+    // DTLS timeout
+    std::chrono::steady_clock::duration timeout;
+
+    // Callback for certificate checkings
+    std::function<int(unsigned status,
+                      const gnutls_datum_t* cert_list,
+                      unsigned cert_list_size)> cert_check;
+};
+
+// Compute Diffie-Hellman parameters suitable for TlsParams::dh_params.
+// Synchonous call: turn it asynchronous with std::async(std::launch::async, newDhParams)
+TlsParams::DhParams newDhParams();
+
+/**
+ * TlsSession
+ *
+ * Manages a DTLS connection over an ICE transport.
+ * This implementation uses a Threadloop to manage IO from ICE and TLS states,
+ * so IO are asynchronous.
+ */
+class TlsSession {
+public:
+    using OnStateChangeFunc = std::function<void(TlsSessionState)>;
+    using OnRxDataFunc = std::function<void(std::vector<uint8_t>&&)>;
+    using OnCertificatesUpdate = std::function<void(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int)>;
+    using VerifyCertificate = std::function<int(gnutls_session_t)>;
+    using TxDataCompleteFunc = std::function<void(std::size_t bytes_sent)>;
+
+    // ===> WARNINGS <===
+    // Following callbacks are called into the FSM thread context
+    // Do not call blocking routines inside them.
+    using TlsSessionCallbacks = struct {
+        OnStateChangeFunc onStateChange;
+        OnRxDataFunc onRxData;
+        OnCertificatesUpdate onCertificatesUpdate;
+        VerifyCertificate verifyCertificate;
+    };
+
+    TlsSession(std::shared_ptr<IceTransport> ice, int ice_comp_id, const TlsParams& params,
+               const TlsSessionCallbacks& cbs);
+    ~TlsSession();
+
+    // Returns the TLS session type ('server' or 'client')
+    const char* typeName() const;
+
+    // Request TLS thread to stop and quit. IO are not possible after that.
+    void shutdown();
+
+    // Can be called by onStateChange callback when state == ESTABLISHED
+    // to obtain the used cypher suite id.
+    // Return the name of current cipher.
+    const char* getCurrentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const;
+
+    // Asynchronous sending operation. on_send_complete will be called with a positive number
+    // for number of bytes sent, or negative for errors, or 0 in case of shutdown (end of session).
+    ssize_t async_send(void* data, std::size_t size, TxDataCompleteFunc on_send_complete);
+
+private:
+    using clock = std::chrono::steady_clock;
+    using StateHandler = std::function<TlsSessionState(TlsSessionState state)>;
+
+    // Constants (ctor init.)
+    const std::unique_ptr<IceSocket> socket_;
+    const bool isServer_;
+    const TlsParams params_;
+    const TlsSessionCallbacks callbacks_;
+
+    // State machine
+    TlsSessionState handleStateSetup(TlsSessionState state);
+    TlsSessionState handleStateCookie(TlsSessionState state);
+    TlsSessionState handleStateHandshake(TlsSessionState state);
+    TlsSessionState handleStateEstablished(TlsSessionState state);
+    TlsSessionState handleStateShutdown(TlsSessionState state);
+    std::map<TlsSessionState, StateHandler> fsmHandlers_ {};
+    std::atomic<TlsSessionState> state_ {TlsSessionState::SETUP};
+
+    // IO GnuTLS <-> ICE
+    struct TxData {
+        void* const ptr;
+        std::size_t size;
+        TxDataCompleteFunc onComplete;
+    };
+
+    std::mutex ioMutex_ {};
+    std::condition_variable ioCv_ {};
+    std::list<TxData> txQueue_ {};
+    std::list<std::vector<uint8_t>> rxQueue_ {};
+
+    ssize_t send(const TxData&);
+    ssize_t sendRaw(const void*, size_t);
+    ssize_t sendRawVec(const giovec_t*, int);
+    ssize_t recvRaw(void*, size_t);
+    int waitForRawData(unsigned);
+
+    // Statistics (also protected by mutex ioMutex_)
+    std::size_t stRxRawPacketCnt_ {0};
+    std::size_t stRxRawBytesCnt_ {0};
+    std::size_t stRxRawPacketDropCnt_ {0};
+    std::size_t stTxRawPacketCnt_ {0};
+    std::size_t stTxRawBytesCnt_ {0};
+    void dump_io_stats() const;
+
+    // GnuTLS backend and connection state
+    class TlsCertificateCredendials;
+    std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init.
+    gnutls_session_t session_ {nullptr};
+    gnutls_datum_t cookie_key_ {nullptr, 0};
+    gnutls_priority_t priority_cache_ {nullptr};
+    gnutls_dtls_prestate_st prestate_ {};
+    ssize_t cookie_count_ {0};
+
+    TlsSessionState setupClient();
+    TlsSessionState setupServer();
+    void initCredentials();
+    bool commonSessionInit();
+
+    // FSM thread (TLS states)
+    ThreadLoop thread_; // ctor init.
+    bool setup();
+    void process();
+    void cleanup();
+};
+
+}} // namespace ring::tls
diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h
index 1d6510046e1e6ee1c4cb821f0e82c401cee7c1ab..5ff5a1d7bac05ab8e39733149cb9b3e2f02c85c7 100644
--- a/src/sip/sip_utils.h
+++ b/src/sip/sip_utils.h
@@ -27,6 +27,8 @@
 
 #include <pjsip/sip_msg.h>
 #include <pjlib.h>
+#include <pj/pool.h>
+#include <pjsip/sip_endpoint.h>
 
 #include <utility>
 #include <string>
@@ -95,6 +97,16 @@ private:
     pjsip_dialog* dialog_;
 };
 
+// Helper on PJSIP memory pool allocation from endpoint
+// This encapsulate the allocated memory pool inside a unique_ptr
+static inline std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>
+smart_alloc_pool(pjsip_endpoint* endpt, const char* const name, pj_size_t initial, pj_size_t inc) {
+    auto pool = pjsip_endpt_create_pool(endpt, name, initial, inc);
+    if (not pool)
+        throw std::bad_alloc();
+    return std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>(pool, pj_pool_release);
+}
+
 }} // namespace ring::sip_utils
 
 #endif // SIP_UTILS_H_