From b768f518f28ba2ea97c7370e108b7b3ae2a90966 Mon Sep 17 00:00:00 2001
From: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
Date: Thu, 30 Nov 2017 22:24:03 -0500
Subject: [PATCH] ice: refactor using pimpl idiom

Continue the compilation firewall work.
Also update code to use C++14 features.

Rationale: prepare the change to a new transport
abstraction over the project.

Change-Id: I4d387bea8447f5dc251a6bd64ecb63b234bd1cdf
Reviewed-by: Olivier Soldano <olivier.soldano@savoirfairelinux.com>
---
 src/ice_transport.cpp | 866 +++++++++++++++++++++++++-----------------
 src/ice_transport.h   | 364 ++++++------------
 2 files changed, 629 insertions(+), 601 deletions(-)

diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index 750f4453df..7ab020daa1 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -28,6 +28,12 @@
 #include <pjlib.h>
 #include <msgpack.hpp>
 
+#include <map>
+#include <atomic>
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+#include <thread>
 #include <utility>
 #include <tuple>
 #include <algorithm>
@@ -36,22 +42,135 @@
 #include <thread>
 #include <cerrno>
 
-#define TRY(ret) do {      \
+#define TRY(ret) do {                                        \
         if ((ret) != PJ_SUCCESS)                             \
-            throw std::runtime_error(#ret " failed");      \
+            throw std::runtime_error(#ret " failed");        \
     } while (0)
 
 namespace ring {
 
 static constexpr unsigned STUN_MAX_PACKET_SIZE {8192};
-static constexpr uint16_t IPV6_HEADER_SIZE = 40; // Size in bytes of IPV6 packet header
-static constexpr uint16_t IPV4_HEADER_SIZE = 20; // Size in bytes of IPV4 packet header
+static constexpr uint16_t IPV6_HEADER_SIZE = 40; ///< Size in bytes of IPV6 packet header
+static constexpr uint16_t IPV4_HEADER_SIZE = 20; ///< Size in bytes of IPV4 packet header
+static constexpr int MAX_CANDIDATES {32};
+static constexpr char NEW_LINE = '\n'; ///< New line character used for (de)serialisation
 
-// TODO: C++14 ? remove me and use std::min
-template< class T >
-static constexpr const T& min( const T& a, const T& b ) {
-    return (b < a) ? b : a;
-}
+//==============================================================================
+
+namespace
+{
+
+struct IceSTransDeleter
+{
+    void operator ()(pj_ice_strans* ptr) {
+        pj_ice_strans_stop_ice(ptr);
+        pj_ice_strans_destroy(ptr);
+    }
+};
+
+} // namespace <anonymous>
+
+//==============================================================================
+
+class IceTransport::Impl
+{
+public:
+    Impl(const char* name, int component_count, bool master, const IceTransportOptions& options);
+    ~Impl();
+
+    void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
+                    pj_status_t status);
+
+    void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size);
+
+    /**
+     * Set/change transport role as initiator.
+     * Should be called before start method.
+     */
+    bool setInitiatorSession();
+
+    /**
+     * Set/change transport role as slave.
+     * Should be called before start method.
+     */
+    bool setSlaveSession();
+
+    bool createIceSession(pj_ice_sess_role role);
+
+    void getUFragPwd();
+
+    void getDefaultCanditates();
+
+    // Non-mutex protected of public versions
+    bool _isInitialized() const;
+    bool _isStarted() const;
+    bool _isRunning() const;
+    bool _isFailed() const;
+
+    IpAddr getLocalAddress(unsigned comp_id) const;
+    IpAddr getRemoteAddress(unsigned comp_id) const;
+
+    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
+    IceTransportCompleteCb on_initdone_cb_;
+    IceTransportCompleteCb on_negodone_cb_;
+    std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_;
+    unsigned component_count_;
+    pj_ice_sess_cand cand_[MAX_CANDIDATES] {};
+    std::string local_ufrag_;
+    std::string local_pwd_;
+    pj_sockaddr remoteAddr_;
+    std::condition_variable iceCV_ {};
+    mutable std::mutex iceMutex_ {};
+    pj_ice_strans_cfg config_;
+    std::string last_errmsg_;
+
+    struct Packet {
+        Packet(void *pkt, pj_size_t size)
+            : data {std::make_unique<char[]>(size)}, datalen {size} {
+            std::copy_n(reinterpret_cast<char*>(pkt), size, data.get());
+        }
+        std::unique_ptr<char[]> data;
+        size_t datalen;
+    };
+
+    struct ComponentIO {
+        std::mutex mutex;
+        std::condition_variable cv;
+        std::deque<Packet> queue;
+        IceRecvCb cb;
+    };
+
+    std::vector<ComponentIO> compIO_;
+
+    std::atomic_bool initiatorSession_ {true};
+
+    /**
+     * Returns the IP of each candidate for a given component in the ICE session
+     */
+    std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const;
+
+    /**
+     * Adds a reflective candidate to ICE session
+     * Must be called before negotiation
+     */
+    void addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr);
+
+    /**
+     * Creates UPnP port mappings and adds ICE candidates based on those mappings
+     */
+    void selectUPnPIceCandidates();
+
+    std::unique_ptr<upnp::Controller> upnp_;
+
+    bool onlyIPv4Private_ {true};
+
+    // IO/Timer events are handled by following thread
+    std::thread thread_;
+    std::atomic_bool threadTerminateFlags_ {false};
+    void handleEvents(unsigned max_msec);
+};
+
+//==============================================================================
 
 /**
  * Add stun/turn servers or default host as candidates
@@ -128,41 +247,10 @@ add_turn_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const TurnServerInfo& i
     RING_DBG("[ice] added turn server '%s', port %d", pj_strbuf(&turn.server), turn.port);
 }
 
-//##################################################################################################
-
-IceTransport::Packet::Packet(void *pkt, pj_size_t size)
-    : data(new char[size]), datalen(size)
-{
-    std::copy_n(reinterpret_cast<char*>(pkt), size, data.get());
-}
-
-void
-IceTransport::cb_on_rx_data(pj_ice_strans* ice_st,
-                            unsigned comp_id,
-                            void *pkt, pj_size_t size,
-                            const pj_sockaddr_t* /*src_addr*/,
-                            unsigned /*src_addr_len*/)
-{
-    if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st)))
-        tr->onReceiveData(comp_id, pkt, size);
-    else
-        RING_WARN("null IceTransport");
-}
-
-void
-IceTransport::cb_on_ice_complete(pj_ice_strans* ice_st,
-                                 pj_ice_strans_op op,
-                                 pj_status_t status)
-{
-    if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st)))
-        tr->onComplete(ice_st, op, status);
-    else
-        RING_WARN("null IceTransport");
-}
-
+//==============================================================================
 
-IceTransport::IceTransport(const char* name, int component_count, bool master,
-                           const IceTransportOptions& options)
+IceTransport::Impl::Impl(const char* name, int component_count, bool master,
+                         const IceTransportOptions& options)
     : pool_(nullptr, pj_pool_release)
     , on_initdone_cb_(options.onInitDone)
     , on_negodone_cb_(options.onNegoDone)
@@ -184,8 +272,23 @@ IceTransport::IceTransport(const char* name, int component_count, bool master,
 
     pj_ice_strans_cb icecb;
     pj_bzero(&icecb, sizeof(icecb));
-    icecb.on_rx_data = cb_on_rx_data;
-    icecb.on_ice_complete = cb_on_ice_complete;
+
+    icecb.on_rx_data =                                                  \
+        [] (pj_ice_strans* ice_st, unsigned comp_id, void *pkt, pj_size_t size,
+            const pj_sockaddr_t* /*src_addr*/, unsigned /*src_addr_len*/) {
+        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
+            tr->onReceiveData(comp_id, pkt, size);
+        else
+            RING_WARN("null IceTransport");
+    };
+
+    icecb.on_ice_complete = \
+        [] (pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status) {
+        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
+            tr->onComplete(ice_st, op, status);
+        else
+            RING_WARN("null IceTransport");
+    };
 
     // Add STUN servers
     for (auto& server : options.stunServers)
@@ -195,7 +298,7 @@ IceTransport::IceTransport(const char* name, int component_count, bool master,
     for (auto& server : options.turnServers)
         add_turn_server(*pool_, config_, server);
 
-    static constexpr auto IOQUEUE_MAX_HANDLES = min(PJ_IOQUEUE_MAX_HANDLES, 64);
+    static constexpr auto IOQUEUE_MAX_HANDLES = std::min(PJ_IOQUEUE_MAX_HANDLES, 64);
     TRY( pj_timer_heap_create(pool_.get(), 100, &config_.stun_cfg.timer_heap) );
     TRY( pj_ioqueue_create(pool_.get(), IOQUEUE_MAX_HANDLES, &config_.stun_cfg.ioqueue) );
 
@@ -216,7 +319,7 @@ IceTransport::IceTransport(const char* name, int component_count, bool master,
         });
 }
 
-IceTransport::~IceTransport()
+IceTransport::Impl::~Impl()
 {
     sip_utils::register_thread();
 
@@ -233,8 +336,46 @@ IceTransport::~IceTransport()
         pj_timer_heap_destroy(config_.stun_cfg.timer_heap);
 }
 
+bool
+IceTransport::Impl::_isInitialized() const
+{
+    if (auto icest = icest_.get()) {
+        auto state = pj_ice_strans_get_state(icest);
+        return state >= PJ_ICE_STRANS_STATE_SESS_READY and state != PJ_ICE_STRANS_STATE_FAILED;
+    }
+    return false;
+}
+
+bool
+IceTransport::Impl::_isStarted() const
+{
+    if (auto icest = icest_.get()) {
+        auto state = pj_ice_strans_get_state(icest);
+        return state >= PJ_ICE_STRANS_STATE_NEGO and state != PJ_ICE_STRANS_STATE_FAILED;
+    }
+    return false;
+}
+
+bool
+IceTransport::Impl::_isRunning() const
+{
+    if (auto icest = icest_.get()) {
+        auto state = pj_ice_strans_get_state(icest);
+        return state >= PJ_ICE_STRANS_STATE_RUNNING and state != PJ_ICE_STRANS_STATE_FAILED;
+    }
+    return false;
+}
+
+bool
+IceTransport::Impl::_isFailed() const
+{
+    if (auto icest = icest_.get())
+        return pj_ice_strans_get_state(icest) == PJ_ICE_STRANS_STATE_FAILED;
+    return false;
+}
+
 void
-IceTransport::handleEvents(unsigned max_msec)
+IceTransport::Impl::handleEvents(unsigned max_msec)
 {
     // By tests, never seen more than two events per 500ms
     static constexpr auto MAX_NET_EVENTS = 2;
@@ -277,8 +418,7 @@ IceTransport::handleEvents(unsigned max_msec)
 }
 
 void
-IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
-                         pj_status_t status)
+IceTransport::Impl::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status)
 {
     const char *opname =
         op == PJ_ICE_STRANS_OP_INIT ? "initialization" :
@@ -308,7 +448,7 @@ IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
     }
 
     if (op == PJ_ICE_STRANS_OP_INIT and on_initdone_cb_)
-        on_initdone_cb_(*this, done);
+        on_initdone_cb_(done);
     else if (op == PJ_ICE_STRANS_OP_NEGOTIATION) {
         if (done) {
             // Dump of connection pairs
@@ -329,95 +469,326 @@ IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
             RING_DBG("[ice:%p] connection pairs (local <-> remote):\n%s", this, out.str().c_str());
         }
         if (on_negodone_cb_)
-            on_negodone_cb_(*this, done);
+            on_negodone_cb_(done);
     }
 
     // Unlock waitForXXX APIs
     iceCV_.notify_all();
 }
 
+bool
+IceTransport::Impl::setInitiatorSession()
+{
+    RING_DBG("ICE as master");
+    initiatorSession_ = true;
+    if (_isInitialized()) {
+        auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLING);
+        if (status != PJ_SUCCESS) {
+            last_errmsg_ = sip_utils::sip_strerror(status);
+            RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
+            return false;
+        }
+        return true;
+    }
+    return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING);
+}
+
+bool
+IceTransport::Impl::setSlaveSession()
+{
+    RING_DBG("ICE as slave");
+    initiatorSession_ = false;
+    if (_isInitialized()) {
+        auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLED);
+        if (status != PJ_SUCCESS) {
+            last_errmsg_ = sip_utils::sip_strerror(status);
+            RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
+            return false;
+        }
+        return true;
+    }
+    return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED);
+}
+
+IpAddr
+IceTransport::Impl::getLocalAddress(unsigned comp_id) const
+{
+    // Return the local IP of negotiated connection pair
+    if (_isRunning()) {
+        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1))
+            return sess->lcand->addr;
+        else
+            return {}; // disabled component
+    } else
+        RING_WARN("[ice:%p] bad call: non-negotiated transport", this);
+
+    // Return the default IP (could be not nominated and valid after negotiation)
+    if (_isInitialized())
+        return cand_[comp_id].addr;
+
+    RING_ERR("[ice:%p] bad call: non-initialized transport", this);
+    return {};
+}
+
+IpAddr
+IceTransport::Impl::getRemoteAddress(unsigned comp_id) const
+{
+    // Return the remote IP of negotiated connection pair
+    if (_isRunning()) {
+        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1))
+            return sess->rcand->addr;
+        else
+            return {}; // disabled component
+    } else
+        RING_WARN("[ice:%p] bad call: non-negotiated transport", this);
+
+    RING_ERR("[ice:%p] bad call: non-negotiated transport", this);
+    return {};
+}
+
 void
-IceTransport::getUFragPwd()
+IceTransport::Impl::getUFragPwd()
 {
     pj_str_t local_ufrag, local_pwd;
-    pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, NULL, NULL);
+    pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, nullptr, nullptr);
     local_ufrag_.assign(local_ufrag.ptr, local_ufrag.slen);
     local_pwd_.assign(local_pwd.ptr, local_pwd.slen);
 }
 
-std::string
-IceTransport::getLastErrMsg() const
+void
+IceTransport::Impl::getDefaultCanditates()
+{
+    for (unsigned i=0; i < component_count_; ++i)
+        pj_ice_strans_get_def_cand(icest_.get(), i+1, &cand_[i]);
+}
+
+bool
+IceTransport::Impl::createIceSession(pj_ice_sess_role role)
+{
+    if (pj_ice_strans_init_ice(icest_.get(), role, nullptr, nullptr) != PJ_SUCCESS) {
+        RING_ERR("[ice:%p] pj_ice_strans_init_ice() failed", this);
+        return false;
+    }
+
+    // Fetch some information on local configuration
+    getUFragPwd();
+    getDefaultCanditates();
+    RING_DBG("[ice:%p] (local) ufrag=%s, pwd=%s", this, local_ufrag_.c_str(), local_pwd_.c_str());
+    return true;
+}
+
+std::vector<IpAddr>
+IceTransport::Impl::getLocalCandidatesAddr(unsigned comp_id) const
+{
+    std::vector<IpAddr> cand_addrs;
+    pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)];
+    unsigned cand_cnt = PJ_ARRAY_SIZE(cand);
+
+    if (pj_ice_strans_enum_cands(icest_.get(), comp_id, &cand_cnt, cand) != PJ_SUCCESS) {
+        RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this);
+        return cand_addrs;
+    }
+
+    for (unsigned i=0; i<cand_cnt; ++i)
+        cand_addrs.push_back(cand[i].addr);
+
+    return cand_addrs;
+}
+
+void
+IceTransport::Impl::addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr)
+{
+    // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK
+    // WARNING: following implementation is a HACK of PJNATH !!
+    // ice_strans doesn't have any API that permit to inject ICE any kind of candidates.
+    // So, the hack consists in accessing hidden ICE session using a patched PJPNATH
+    // library with a new API exposing this session (pj_ice_strans_get_ice_sess).
+    // Then call pj_ice_sess_add_cand() with a carfully forged candidate:
+    // the transport_id field uses an index in ICE transport STUN servers array
+    // corresponding to a STUN server with the same address familly.
+    // This implies we hope they'll not be modification of transport_id meaning in future
+    // and no conflics with the borrowed STUN config.
+    // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK
+
+    // borrowed from pjproject/pjnath/ice_strans.c, modified to be C++11'ized.
+    static auto CREATE_TP_ID = [](pj_uint8_t type, pj_uint8_t idx) {
+        return (pj_uint8_t)((type << 6) | idx);
+    };
+    static constexpr int SRFLX_PREF = 65535;
+    static constexpr int TP_STUN = 1;
+
+    // find a compatible STUN host with same address familly, normally all system enabled
+    // host addresses are represented, so we expect to always found this host
+    int idx = -1;
+    auto af = addr.getFamily();
+    if (af == AF_UNSPEC) {
+        RING_ERR("[ice:%p] Unable to add reflective IP %s: unknown addess familly", this,
+                 addr.toString().c_str());
+        return;
+    }
+
+    for (unsigned i=0; i < config_.stun_tp_cnt; ++i) {
+        if (config_.stun_tp[i].af == af) {
+            idx = i;
+            break;
+        }
+    }
+    if (idx < 0) {
+        RING_ERR("[ice:%p] Unable to add reflective IP %s: no suitable local STUN host found", this,
+                 addr.toString().c_str());
+        return;
+    }
+
+    pj_ice_sess_cand cand;
+
+    cand.type = PJ_ICE_CAND_TYPE_SRFLX;
+    cand.status = PJ_EPENDING; // not used
+    cand.comp_id = comp_id;
+    cand.transport_id = CREATE_TP_ID(TP_STUN, idx); // HACK!!
+    cand.local_pref = SRFLX_PREF; // reflective
+    /* cand.foundation = ? */
+    /* cand.prio = calculated by ice session */
+    /* make base and addr the same since we're not going through a server */
+    pj_sockaddr_cp(&cand.base_addr, base.pjPtr());
+    pj_sockaddr_cp(&cand.addr, addr.pjPtr());
+    pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr);
+    pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr);
+
+    auto ret = pj_ice_sess_add_cand(pj_ice_strans_get_ice_sess(icest_.get()),
+        cand.comp_id,
+        cand.transport_id,
+        cand.type,
+        cand.local_pref,
+        &cand.foundation,
+        &cand.addr,
+        &cand.base_addr,
+        &cand.rel_addr,
+        pj_sockaddr_get_len(&cand.addr),
+        NULL);
+
+    if (ret != PJ_SUCCESS) {
+        last_errmsg_ = sip_utils::sip_strerror(ret);
+        RING_ERR("[ice:%p] pj_ice_sess_add_cand failed with error %d: %s", this, ret,
+                 last_errmsg_.c_str());
+        RING_ERR("[ice:%p] failed to add candidate for comp_id=%d : %s : %s", this, comp_id,
+                 base.toString().c_str(), addr.toString().c_str());
+    } else {
+        RING_DBG("[ice:%p] succeed to add candidate for comp_id=%d : %s : %s", this, comp_id,
+                 base.toString().c_str(), addr.toString().c_str());
+    }
+}
+
+void
+IceTransport::Impl::selectUPnPIceCandidates()
+{
+    /* use upnp to open ports and add the proper candidates */
+    if (upnp_) {
+        /* for every component, get the candidate(s)
+         * create a port mapping either with that port, or with an available port
+         * add candidate with that port and public IP
+         */
+        if (auto publicIP = upnp_->getExternalIP()) {
+            /* comp_id start at 1 */
+            for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) {
+                RING_DBG("[ice:%p] UPnP: Opening port(s) for ICE comp %d and adding candidate with public IP",
+                         this, comp_id);
+                auto candidates = getLocalCandidatesAddr(comp_id);
+                for (IpAddr addr : candidates) {
+                    auto localIP = upnp_->getLocalIP();
+                    localIP.setPort(addr.getPort());
+                    if (addr != localIP)
+                        continue;
+                    uint16_t port = addr.getPort();
+                    uint16_t port_used;
+                    if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) {
+                        publicIP.setPort(port_used);
+                        addReflectiveCandidate(comp_id, addr, publicIP);
+                    } else
+                        RING_WARN("[ice:%p] UPnP: Could not create a port mapping for the ICE candide", this);
+                }
+            }
+        } else {
+            RING_WARN("[ice:%p] UPnP: Could not determine public IP for ICE candidates", this);
+        }
+    }
+}
+
+void
+IceTransport::Impl::onReceiveData(unsigned comp_id, void *pkt, pj_size_t size)
+{
+    if (!comp_id or comp_id > component_count_) {
+        RING_ERR("rx: invalid comp_id (%u)", comp_id);
+        return;
+    }
+    if (!size)
+        return;
+    auto& io = compIO_[comp_id-1];
+    std::lock_guard<std::mutex> lk(io.mutex);
+    if (io.cb) {
+        io.cb((uint8_t*)pkt, size);
+    } else {
+        io.queue.emplace_back(pkt, size);
+        io.cv.notify_one();
+    }
+}
+
+//==============================================================================
+
+IceTransport::IceTransport(const char* name, int component_count, bool master,
+                           const IceTransportOptions& options)
+    : pimpl_ {std::make_unique<Impl>(name, component_count, master, options)}
+{}
+
+bool
+IceTransport::isInitialized() const
 {
-    return last_errmsg_;
+    std::lock_guard<std::mutex> lk(pimpl_->iceMutex_);
+    return pimpl_->_isInitialized();
 }
 
-void
-IceTransport::getDefaultCanditates()
+bool
+IceTransport::isStarted() const
 {
-    for (unsigned i=0; i < component_count_; ++i)
-        pj_ice_strans_get_def_cand(icest_.get(), i+1, &cand_[i]);
+    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
+    return pimpl_->_isStarted();
 }
 
 bool
-IceTransport::createIceSession(pj_ice_sess_role role)
+IceTransport::isRunning() const
 {
-    if (pj_ice_strans_init_ice(icest_.get(), role, nullptr, nullptr) != PJ_SUCCESS) {
-        RING_ERR("[ice:%p] pj_ice_strans_init_ice() failed", this);
-        return false;
-    }
-
-    // Fetch some information on local configuration
-    getUFragPwd();
-    getDefaultCanditates();
-    RING_DBG("[ice:%p] (local) ufrag=%s, pwd=%s", this, local_ufrag_.c_str(), local_pwd_.c_str());
-    return true;
+    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
+    return pimpl_->_isRunning();
 }
 
 bool
-IceTransport::setInitiatorSession()
+IceTransport::isFailed() const
 {
-    RING_DBG("ICE as master");
-    initiatorSession_ = true;
-    if (isInitialized()) {
-        auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLING);
-        if (status != PJ_SUCCESS) {
-            last_errmsg_ = sip_utils::sip_strerror(status);
-            RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
-            return false;
-        }
-        return true;
-    }
-    return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING);
+    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
+    return pimpl_->_isFailed();
 }
 
-bool
-IceTransport::setSlaveSession()
+unsigned
+IceTransport::getComponentCount() const
 {
-    RING_DBG("ICE as slave");
-    initiatorSession_ = false;
-    if (isInitialized()) {
-        auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLED);
-        if (status != PJ_SUCCESS) {
-            last_errmsg_ = sip_utils::sip_strerror(status);
-            RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
-            return false;
-        }
-        return true;
-    }
-    return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED);
+    return pimpl_->component_count_;
+}
+
+std::string
+IceTransport::getLastErrMsg() const
+{
+    return pimpl_->last_errmsg_;
 }
 
 bool
 IceTransport::isInitiator() const
 {
     if (isInitialized())
-        return pj_ice_strans_get_role(icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING;
-    return initiatorSession_;
+        return pj_ice_strans_get_role(pimpl_->icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING;
+    return pimpl_->initiatorSession_;
 }
 
 bool
-IceTransport::start(const Attribute& rem_attrs,
-                    const std::vector<IceCandidate>& rem_candidates)
+IceTransport::start(const Attribute& rem_attrs, const std::vector<IceCandidate>& rem_candidates)
 {
     if (not isInitialized()) {
         RING_ERR("[ice:%p] not initialized transport", this);
@@ -432,14 +803,14 @@ IceTransport::start(const Attribute& rem_attrs,
 
     pj_str_t ufrag, pwd;
     RING_DBG("[ice:%p] negotiation starting (%zu remote candidates)", this, rem_candidates.size());
-    auto status = pj_ice_strans_start_ice(icest_.get(),
+    auto status = pj_ice_strans_start_ice(pimpl_->icest_.get(),
                                           pj_cstr(&ufrag, rem_attrs.ufrag.c_str()),
                                           pj_cstr(&pwd, rem_attrs.pwd.c_str()),
                                           rem_candidates.size(),
                                           rem_candidates.data());
     if (status != PJ_SUCCESS) {
-        last_errmsg_ = sip_utils::sip_strerror(status);
-        RING_ERR("[ice:%p] start failed: %s", this, last_errmsg_.c_str());
+        pimpl_->last_errmsg_ = sip_utils::sip_strerror(status);
+        RING_ERR("[ice:%p] start failed: %s", this, pimpl_->last_errmsg_.c_str());
         return false;
     }
     return true;
@@ -482,12 +853,12 @@ IceTransport::start(const std::vector<uint8_t>& rem_data)
         return false;
     }
 
-    if (rem_pwd.empty() or rem_pwd.empty() or rem_candidates.empty()) {
+    if (rem_ufrag.empty() or rem_pwd.empty() or rem_candidates.empty()) {
         RING_ERR("[ice:%p] invalid remote attributes", this);
         return false;
     }
 
-    if (onlyIPv4Private_)
+    if (pimpl_->onlyIPv4Private_)
         RING_WARN("[ice:%p] no public IPv4 found, your connection may fail!", this);
 
     return start({rem_ufrag, rem_pwd}, rem_candidates);
@@ -497,104 +868,42 @@ bool
 IceTransport::stop()
 {
     if (isStarted()) {
-        auto status = pj_ice_strans_stop_ice(icest_.get());
+        auto status = pj_ice_strans_stop_ice(pimpl_->icest_.get());
         if (status != PJ_SUCCESS) {
-            last_errmsg_ = sip_utils::sip_strerror(status);
-            RING_ERR("ICE stop failed: %s", last_errmsg_.c_str());
+            pimpl_->last_errmsg_ = sip_utils::sip_strerror(status);
+            RING_ERR("ICE stop failed: %s", pimpl_->last_errmsg_.c_str());
             return false;
         }
     }
     return true;
 }
 
-bool
-IceTransport::_isInitialized() const
-{
-    if (auto icest = icest_.get()) {
-        auto state = pj_ice_strans_get_state(icest);
-        return state >= PJ_ICE_STRANS_STATE_SESS_READY and state != PJ_ICE_STRANS_STATE_FAILED;
-    }
-    return false;
-}
-
-bool
-IceTransport::_isStarted() const
-{
-    if (auto icest = icest_.get()) {
-        auto state = pj_ice_strans_get_state(icest);
-        return state >= PJ_ICE_STRANS_STATE_NEGO and state != PJ_ICE_STRANS_STATE_FAILED;
-    }
-    return false;
-}
-
-bool
-IceTransport::_isRunning() const
-{
-    if (auto icest = icest_.get()) {
-        auto state = pj_ice_strans_get_state(icest);
-        return state >= PJ_ICE_STRANS_STATE_RUNNING and state != PJ_ICE_STRANS_STATE_FAILED;
-    }
-    return false;
-}
-
-bool
-IceTransport::_isFailed() const
-{
-    if (auto icest = icest_.get())
-        return pj_ice_strans_get_state(icest) == PJ_ICE_STRANS_STATE_FAILED;
-    return false;
-}
-
 IpAddr
 IceTransport::getLocalAddress(unsigned comp_id) const
 {
-    // Return the local IP of negotiated connection pair
-    if (isRunning()) {
-        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1))
-            return sess->lcand->addr;
-        else
-            return {}; // disabled component
-    } else
-        RING_WARN("[ice:%p] bad call: non-negotiated transport", this);
-
-    // Return the default IP (could be not nominated and valid after negotiation)
-    if (isInitialized())
-        return cand_[comp_id].addr;
-
-    RING_ERR("[ice:%p] bad call: non-initialized transport", this);
-    return {};
+    return pimpl_->getLocalAddress(comp_id);
 }
 
 IpAddr
 IceTransport::getRemoteAddress(unsigned comp_id) const
 {
-    // Return the remote IP of negotiated connection pair
-    if (isRunning()) {
-        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1))
-            return sess->rcand->addr;
-        else
-            return {}; // disabled component
-    } else
-        RING_WARN("[ice:%p] bad call: non-negotiated transport", this);
-
-    RING_ERR("[ice:%p] bad call: non-negotiated transport", this);
-    return {};
+    return pimpl_->getRemoteAddress(comp_id);
 }
 
 const IceTransport::Attribute
 IceTransport::getLocalAttributes() const
 {
-    return {local_ufrag_, local_pwd_};
+    return {pimpl_->local_ufrag_, pimpl_->local_pwd_};
 }
 
 std::vector<std::string>
 IceTransport::getLocalCandidates(unsigned comp_id) const
 {
     std::vector<std::string> res;
-    pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)];
+    pj_ice_sess_cand cand[PJ_ARRAY_SIZE(pimpl_->cand_)];
     unsigned cand_cnt = PJ_ARRAY_SIZE(cand);
 
-    if (pj_ice_strans_enum_cands(icest_.get(), comp_id+1, &cand_cnt, cand) != PJ_SUCCESS) {
+    if (pj_ice_strans_enum_cands(pimpl_->icest_.get(), comp_id+1, &cand_cnt, cand) != PJ_SUCCESS) {
         RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this);
         return res;
     }
@@ -615,24 +924,6 @@ IceTransport::getLocalCandidates(unsigned comp_id) const
     return res;
 }
 
-std::vector<IpAddr>
-IceTransport::getLocalCandidatesAddr(unsigned comp_id) const
-{
-    std::vector<IpAddr> cand_addrs;
-    pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)];
-    unsigned cand_cnt = PJ_ARRAY_SIZE(cand);
-
-    if (pj_ice_strans_enum_cands(icest_.get(), comp_id, &cand_cnt, cand) != PJ_SUCCESS) {
-        RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this);
-        return cand_addrs;
-    }
-
-    for (unsigned i=0; i<cand_cnt; ++i)
-        cand_addrs.push_back(cand[i].addr);
-
-    return cand_addrs;
-}
-
 bool
 IceTransport::registerPublicIP(unsigned compId, const IpAddr& publicIP)
 {
@@ -649,136 +940,18 @@ IceTransport::registerPublicIP(unsigned compId, const IpAddr& publicIP)
     // negotiation, only to exchanged candidates between peers.
     auto localIP = ip_utils::getLocalAddr(publicIP.getFamily());
     auto pubIP = publicIP;
-    for (const auto& addr : getLocalCandidatesAddr(compId)) {
+    for (const auto& addr : pimpl_->getLocalCandidatesAddr(compId)) {
         auto port = addr.getPort();
         localIP.setPort(port);
         if (addr != localIP)
             continue;
         pubIP.setPort(port);
-        addReflectiveCandidate(compId, addr, pubIP);
+        pimpl_->addReflectiveCandidate(compId, addr, pubIP);
         return true;
     }
     return false;
 }
 
-void
-IceTransport::addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr)
-{
-    // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK
-    // WARNING: following implementation is a HACK of PJNATH !!
-    // ice_strans doesn't have any API that permit to inject ICE any kind of candidates.
-    // So, the hack consists in accessing hidden ICE session using a patched PJPNATH
-    // library with a new API exposing this session (pj_ice_strans_get_ice_sess).
-    // Then call pj_ice_sess_add_cand() with a carfully forged candidate:
-    // the transport_id field uses an index in ICE transport STUN servers array
-    // corresponding to a STUN server with the same address familly.
-    // This implies we hope they'll not be modification of transport_id meaning in future
-    // and no conflics with the borrowed STUN config.
-    // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK
-
-    // borrowed from pjproject/pjnath/ice_strans.c, modified to be C++11'ized.
-    static auto CREATE_TP_ID = [](pj_uint8_t type, pj_uint8_t idx) {
-        return (pj_uint8_t)((type << 6) | idx);
-    };
-    static constexpr int SRFLX_PREF = 65535;
-    static constexpr int TP_STUN = 1;
-
-    // find a compatible STUN host with same address familly, normally all system enabled
-    // host addresses are represented, so we expect to always found this host
-    int idx = -1;
-    auto af = addr.getFamily();
-    if (af == AF_UNSPEC) {
-        RING_ERR("[ice:%p] Unable to add reflective IP %s: unknown addess familly", this,
-                 addr.toString().c_str());
-        return;
-    }
-
-    for (unsigned i=0; i < config_.stun_tp_cnt; ++i) {
-        if (config_.stun_tp[i].af == af) {
-            idx = i;
-            break;
-        }
-    }
-    if (idx < 0) {
-        RING_ERR("[ice:%p] Unable to add reflective IP %s: no suitable local STUN host found", this,
-                 addr.toString().c_str());
-        return;
-    }
-
-    pj_ice_sess_cand cand;
-
-    cand.type = PJ_ICE_CAND_TYPE_SRFLX;
-    cand.status = PJ_EPENDING; // not used
-    cand.comp_id = comp_id;
-    cand.transport_id = CREATE_TP_ID(TP_STUN, idx); // HACK!!
-    cand.local_pref = SRFLX_PREF; // reflective
-    /* cand.foundation = ? */
-    /* cand.prio = calculated by ice session */
-    /* make base and addr the same since we're not going through a server */
-    pj_sockaddr_cp(&cand.base_addr, base.pjPtr());
-    pj_sockaddr_cp(&cand.addr, addr.pjPtr());
-    pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr);
-    pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr);
-
-    auto ret = pj_ice_sess_add_cand(pj_ice_strans_get_ice_sess(icest_.get()),
-        cand.comp_id,
-        cand.transport_id,
-        cand.type,
-        cand.local_pref,
-        &cand.foundation,
-        &cand.addr,
-        &cand.base_addr,
-        &cand.rel_addr,
-        pj_sockaddr_get_len(&cand.addr),
-        NULL);
-
-    if (ret != PJ_SUCCESS) {
-        last_errmsg_ = sip_utils::sip_strerror(ret);
-        RING_ERR("[ice:%p] pj_ice_sess_add_cand failed with error %d: %s", this, ret,
-                 last_errmsg_.c_str());
-        RING_ERR("[ice:%p] failed to add candidate for comp_id=%d : %s : %s", this, comp_id,
-                 base.toString().c_str(), addr.toString().c_str());
-    } else {
-        RING_DBG("[ice:%p] succeed to add candidate for comp_id=%d : %s : %s", this, comp_id,
-                 base.toString().c_str(), addr.toString().c_str());
-    }
-}
-
-void
-IceTransport::selectUPnPIceCandidates()
-{
-    /* use upnp to open ports and add the proper candidates */
-    if (upnp_) {
-        /* for every component, get the candidate(s)
-         * create a port mapping either with that port, or with an available port
-         * add candidate with that port and public IP
-         */
-        if (auto publicIP = upnp_->getExternalIP()) {
-            /* comp_id start at 1 */
-            for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) {
-                RING_DBG("[ice:%p] UPnP: Opening port(s) for ICE comp %d and adding candidate with public IP",
-                         this, comp_id);
-                auto candidates = getLocalCandidatesAddr(comp_id);
-                for (IpAddr addr : candidates) {
-                    auto localIP = upnp_->getLocalIP();
-                    localIP.setPort(addr.getPort());
-                    if (addr != localIP)
-                        continue;
-                    uint16_t port = addr.getPort();
-                    uint16_t port_used;
-                    if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) {
-                        publicIP.setPort(port_used);
-                        addReflectiveCandidate(comp_id, addr, publicIP);
-                    } else
-                        RING_WARN("[ice:%p] UPnP: Could not create a port mapping for the ICE candide", this);
-                }
-            }
-        } else {
-            RING_WARN("[ice:%p] UPnP: Could not determine public IP for ICE candidates", this);
-        }
-    }
-}
-
 std::vector<uint8_t>
 IceTransport::packIceMsg() const
 {
@@ -789,34 +962,15 @@ IceTransport::packIceMsg() const
 
     std::stringstream ss;
     msgpack::pack(ss, ICE_MSG_VERSION);
-    msgpack::pack(ss, std::make_pair(local_ufrag_, local_pwd_));
-    msgpack::pack(ss, static_cast<uint8_t>(component_count_));
-    for (unsigned i=0; i<component_count_; i++)
+    msgpack::pack(ss, std::make_pair(pimpl_->local_ufrag_, pimpl_->local_pwd_));
+    msgpack::pack(ss, static_cast<uint8_t>(pimpl_->component_count_));
+    for (unsigned i=0; i<pimpl_->component_count_; i++)
         msgpack::pack(ss, getLocalCandidates(i));
 
     auto str(ss.str());
     return std::vector<uint8_t>(str.begin(), str.end());
 }
 
-void
-IceTransport::onReceiveData(unsigned comp_id, void *pkt, pj_size_t size)
-{
-    if (!comp_id or comp_id > component_count_) {
-        RING_ERR("rx: invalid comp_id (%u)", comp_id);
-        return;
-    }
-    if (!size)
-        return;
-    auto& io = compIO_[comp_id-1];
-    std::lock_guard<std::mutex> lk(io.mutex);
-    if (io.cb) {
-        io.cb((uint8_t*)pkt, size);
-    } else {
-        io.queue.emplace_back(pkt, size);
-        io.cv.notify_one();
-    }
-}
-
 bool
 IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand)
 {
@@ -859,7 +1013,7 @@ IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand)
         af = pj_AF_INET6();
     else {
         af = pj_AF_INET();
-        onlyIPv4Private_ &= IpAddr(ipaddr).isPrivate();
+        pimpl_->onlyIPv4Private_ &= IpAddr(ipaddr).isPrivate();
     }
 
     tmpaddr = pj_str(ipaddr);
@@ -871,7 +1025,7 @@ IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand)
     }
 
     pj_sockaddr_set_port(&cand.addr, (pj_uint16_t)port);
-    pj_strdup2(pool_.get(), &cand.foundation, foundation);
+    pj_strdup2(pimpl_->pool_.get(), &cand.foundation, foundation);
 
     return true;
 }
@@ -880,7 +1034,7 @@ ssize_t
 IceTransport::recv(int comp_id, unsigned char* buf, size_t len)
 {
     sip_utils::register_thread();
-    auto& io = compIO_[comp_id];
+    auto& io = pimpl_->compIO_[comp_id];
     std::lock_guard<std::mutex> lk(io.mutex);
 
     if (io.queue.empty())
@@ -897,7 +1051,7 @@ IceTransport::recv(int comp_id, unsigned char* buf, size_t len)
 void
 IceTransport::setOnRecv(unsigned comp_id, IceRecvCb cb)
 {
-    auto& io = compIO_[comp_id];
+    auto& io = pimpl_->compIO_[comp_id];
     std::lock_guard<std::mutex> lk(io.mutex);
     io.cb = cb;
 
@@ -919,13 +1073,13 @@ IceTransport::send(int comp_id, const unsigned char* buf, size_t len)
         errno = EINVAL;
         return -1;
     }
-    auto status = pj_ice_strans_sendto(icest_.get(), comp_id+1, buf, len, remote.pjPtr(), remote.getLength());
+    auto status = pj_ice_strans_sendto(pimpl_->icest_.get(), comp_id+1, buf, len, remote.pjPtr(), remote.getLength());
     if (status != PJ_SUCCESS) {
         if (status == PJ_EBUSY) {
             errno = EAGAIN;
         } else {
-            last_errmsg_ = sip_utils::sip_strerror(status);
-            RING_ERR("[ice:%p] ice send failed: %s", this, last_errmsg_.c_str());
+            pimpl_->last_errmsg_ = sip_utils::sip_strerror(status);
+            RING_ERR("[ice:%p] ice send failed: %s", this, pimpl_->last_errmsg_.c_str());
             errno = EIO;
         }
         return -1;
@@ -937,31 +1091,31 @@ IceTransport::send(int comp_id, const unsigned char* buf, size_t len)
 int
 IceTransport::waitForInitialization(unsigned timeout)
 {
-    std::unique_lock<std::mutex> lk(iceMutex_);
-    if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout),
-                         [this]{ return _isInitialized() or _isFailed(); })) {
+    std::unique_lock<std::mutex> lk(pimpl_->iceMutex_);
+    if (!pimpl_->iceCV_.wait_for(lk, std::chrono::seconds(timeout),
+                                 [this]{ return pimpl_->_isInitialized() or pimpl_->_isFailed(); })) {
         RING_WARN("[ice:%p] waitForInitialization: timeout", this);
         return -1;
     }
-    return not _isFailed();
+    return not pimpl_->_isFailed();
 }
 
 int
 IceTransport::waitForNegotiation(unsigned timeout)
 {
-    std::unique_lock<std::mutex> lk(iceMutex_);
-    if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout),
-                         [this]{ return _isRunning() or _isFailed(); })) {
+    std::unique_lock<std::mutex> lk(pimpl_->iceMutex_);
+    if (!pimpl_->iceCV_.wait_for(lk, std::chrono::seconds(timeout),
+                         [this]{ return pimpl_->_isRunning() or pimpl_->_isFailed(); })) {
         RING_WARN("[ice:%p] waitForIceNegotiation: timeout", this);
         return -1;
     }
-    return not _isFailed();
+    return not pimpl_->_isFailed();
 }
 
 ssize_t
 IceTransport::waitForData(int comp_id, unsigned int timeout)
 {
-    auto& io = compIO_[comp_id];
+    auto& io = pimpl_->compIO_[comp_id];
     std::unique_lock<std::mutex> lk(io.mutex);
     if (!io.cv.wait_for(lk, std::chrono::milliseconds(timeout),
                         [this, &io]{ return !io.queue.empty() or !isRunning(); })) {
@@ -972,7 +1126,7 @@ IceTransport::waitForData(int comp_id, unsigned int timeout)
     return io.queue.front().datalen;
 }
 
-//##################################################################################################
+//==============================================================================
 
 IceTransportFactory::IceTransportFactory()
     : cp_()
@@ -1020,6 +1174,8 @@ IceTransportFactory::createTransport(const char* name, int component_count,
     }
 }
 
+//==============================================================================
+
 void
 IceSocket::close()
 {
diff --git a/src/ice_transport.h b/src/ice_transport.h
index 9103e29db8..74d0cf714e 100644
--- a/src/ice_transport.h
+++ b/src/ice_transport.h
@@ -18,8 +18,7 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#ifndef ICE_TRANSPORT_H
-#define ICE_TRANSPORT_H
+#pragma once
 
 #include "ice_socket.h"
 #include "ip_utils.h"
@@ -28,15 +27,9 @@
 #include <pjlib.h>
 #include <pjlib-util.h>
 
-#include <map>
 #include <functional>
 #include <memory>
-#include <atomic>
 #include <vector>
-#include <queue>
-#include <mutex>
-#include <condition_variable>
-#include <thread>
 
 namespace ring {
 
@@ -46,7 +39,7 @@ class Controller;
 
 class IceTransport;
 
-using IceTransportCompleteCb = std::function<void(IceTransport&, bool)>;
+using IceTransportCompleteCb = std::function<void(bool)>;
 using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>;
 using IceCandidate = pj_ice_sess_cand;
 
@@ -77,254 +70,133 @@ struct IceTransportOptions {
 };
 
 class IceTransport {
-    public:
-        using Attribute = struct {
-                std::string ufrag;
-                std::string pwd;
-        };
-
-        /**
-         * Constructor
-         */
-        IceTransport(const char* name, int component_count, bool master,
-                     const IceTransportOptions& options = {});
-
-        /**
-         * Destructor
-         */
-        ~IceTransport();
-
-        /**
-         * Set/change transport role as initiator.
-         * Should be called before start method.
-         */
-        bool setInitiatorSession();
-
-        /**
-         * Set/change transport role as slave.
-         * Should be called before start method.
-         */
-        bool setSlaveSession();
-
-        /**
-         * Get current state
-         */
-        bool isInitiator() const;
-
-        /**
-         * Start tranport negociation between local candidates and given remote
-         * to find the right candidate pair.
-         * This function doesn't block, the callback on_negodone_cb will be called
-         * with the negotiation result when operation is really done.
-         * Return false if negotiation cannot be started else true.
-         */
-        bool start(const Attribute& rem_attrs,
-                   const std::vector<IceCandidate>& rem_candidates);
-        bool start(const std::vector<uint8_t>& attrs_candidates);
-
-        /**
-         * Stop a started or completed transport.
-         */
-        bool stop();
-
-        /**
-         * Returns true if ICE transport has been initialized
-         * [mutex protected]
-         */
-        bool isInitialized() const {
-            std::lock_guard<std::mutex> lk(iceMutex_);
-            return _isInitialized();
-        }
-
-        /**
-         * Returns true if ICE negotiation has been started
-         * [mutex protected]
-         */
-        bool isStarted() const {
-            std::lock_guard<std::mutex> lk(iceMutex_);
-            return _isStarted();
-        }
-
-        /**
-         * Returns true if ICE negotiation has completed with success
-         * [mutex protected]
-         */
-        bool isRunning() const {
-            std::lock_guard<std::mutex> lk(iceMutex_);
-            return _isRunning();
-        }
-
-        /**
-         * Returns true if ICE transport is in failure state
-         * [mutex protected]
-         */
-        bool isFailed() const {
-            std::lock_guard<std::mutex> lk(iceMutex_);
-            return _isFailed();
-        }
-
-        IpAddr getLocalAddress(unsigned comp_id) const;
-
-        IpAddr getRemoteAddress(unsigned comp_id) const;
-
-        std::string getLastErrMsg() const;
-
-        IpAddr getDefaultLocalAddress() const {
-            return getLocalAddress(0);
-        }
-
-        bool registerPublicIP(unsigned compId, const IpAddr& publicIP);
-
-        /**
-         * Return ICE session attributes
-         */
-        const Attribute getLocalAttributes() const;
-
-        /**
-         * Return ICE session attributes
-         */
-        std::vector<std::string> getLocalCandidates(unsigned comp_id) const;
-
-        /**
-         * Returns serialized ICE attributes and candidates.
-         */
-        std::vector<uint8_t> packIceMsg() const;
-
-        bool getCandidateFromSDP(const std::string& line, IceCandidate& cand);
-
-        // I/O methods
-
-        void setOnRecv(unsigned comp_id, IceRecvCb cb);
-
-        ssize_t recv(int comp_id, unsigned char* buf, size_t len);
-
-        ssize_t send(int comp_id, const unsigned char* buf, size_t len);
-
-        int waitForInitialization(unsigned timeout);
-
-        int waitForNegotiation(unsigned timeout);
-
-        ssize_t waitForData(int comp_id, unsigned int timeout);
-
-        unsigned getComponentCount() const {return component_count_;}
-
-    private:
-        static constexpr int MAX_CANDIDATES {32};
-
-        // New line character used for (de)serialisation
-        static constexpr char NEW_LINE = '\n';
-
-        static void cb_on_rx_data(pj_ice_strans *ice_st,
-                                  unsigned comp_id,
-                                  void *pkt, pj_size_t size,
-                                  const pj_sockaddr_t *src_addr,
-                                  unsigned src_addr_len);
-
-        static void cb_on_ice_complete(pj_ice_strans *ice_st,
-                                       pj_ice_strans_op op,
-                                       pj_status_t status);
-
-        struct IceSTransDeleter {
-                void operator ()(pj_ice_strans* ptr) {
-                    pj_ice_strans_stop_ice(ptr);
-                    pj_ice_strans_destroy(ptr);
-                }
-        };
-
-        void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
-                        pj_status_t status);
-
-        void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size);
-
-        bool createIceSession(pj_ice_sess_role role);
-
-        void getUFragPwd();
-
-        void getDefaultCanditates();
-
-        // Non-mutex protected of public versions
-        bool _isInitialized() const;
-        bool _isStarted() const;
-        bool _isRunning() const;
-        bool _isFailed() const;
+public:
+    using Attribute = struct {
+        std::string ufrag;
+        std::string pwd;
+    };
 
-        std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
-        IceTransportCompleteCb on_initdone_cb_;
-        IceTransportCompleteCb on_negodone_cb_;
-        std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_;
-        unsigned component_count_;
-        pj_ice_sess_cand cand_[MAX_CANDIDATES] {};
-        std::string local_ufrag_;
-        std::string local_pwd_;
-        pj_sockaddr remoteAddr_;
-        std::condition_variable iceCV_ {};
-        mutable std::mutex iceMutex_ {};
-        pj_ice_strans_cfg config_;
-        std::string last_errmsg_;
+    /**
+     * Constructor
+     */
+    IceTransport(const char* name, int component_count, bool master,
+                 const IceTransportOptions& options = {});
 
-        struct Packet {
-                Packet(void *pkt, pj_size_t size);
-                std::unique_ptr<char[]> data;
-                size_t datalen;
-        };
-        struct ComponentIO {
-                std::mutex mutex;
-                std::condition_variable cv;
-                std::deque<Packet> queue;
-                IceRecvCb cb;
-        };
-        std::vector<ComponentIO> compIO_;
+    /**
+     * Get current state
+     */
+    bool isInitiator() const;
 
-        std::atomic_bool initiatorSession_ {true};
+    /**
+     * Start tranport negociation between local candidates and given remote
+     * to find the right candidate pair.
+     * This function doesn't block, the callback on_negodone_cb will be called
+     * with the negotiation result when operation is really done.
+     * Return false if negotiation cannot be started else true.
+     */
+    bool start(const Attribute& rem_attrs,
+               const std::vector<IceCandidate>& rem_candidates);
+    bool start(const std::vector<uint8_t>& attrs_candidates);
 
-        /**
-         * Returns the IP of each candidate for a given component in the ICE session
-         */
-        std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const;
+    /**
+     * Stop a started or completed transport.
+     */
+    bool stop();
 
-        /**
-         * Adds a reflective candidate to ICE session
-         * Must be called before negotiation
-         */
-        void addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr);
+    /**
+     * Returns true if ICE transport has been initialized
+     * [mutex protected]
+     */
+    bool isInitialized() const;
 
-        /**
-         * Creates UPnP port mappings and adds ICE candidates based on those mappings
-         */
-        void selectUPnPIceCandidates();
+    /**
+     * Returns true if ICE negotiation has been started
+     * [mutex protected]
+     */
+    bool isStarted() const;
 
-        std::unique_ptr<upnp::Controller> upnp_;
+    /**
+     * Returns true if ICE negotiation has completed with success
+     * [mutex protected]
+     */
+    bool isRunning() const;
 
-        bool onlyIPv4Private_ {true};
-
-        // IO/Timer events are handled by following thread
-        std::thread thread_;
-        std::atomic_bool threadTerminateFlags_ {false};
-        void handleEvents(unsigned max_msec);
+    /**
+     * Returns true if ICE transport is in failure state
+     * [mutex protected]
+     */
+    bool isFailed() const;
+
+    IpAddr getLocalAddress(unsigned comp_id) const;
+
+    IpAddr getRemoteAddress(unsigned comp_id) const;
+
+    std::string getLastErrMsg() const;
+
+    IpAddr getDefaultLocalAddress() const {
+        return getLocalAddress(0);
+    }
+
+    bool registerPublicIP(unsigned compId, const IpAddr& publicIP);
+
+    /**
+     * Return ICE session attributes
+     */
+    const Attribute getLocalAttributes() const;
+
+    /**
+     * Return ICE session attributes
+     */
+    std::vector<std::string> getLocalCandidates(unsigned comp_id) const;
+
+    /**
+     * Returns serialized ICE attributes and candidates.
+     */
+    std::vector<uint8_t> packIceMsg() const;
+
+    bool getCandidateFromSDP(const std::string& line, IceCandidate& cand);
+
+    // I/O methods
+
+    void setOnRecv(unsigned comp_id, IceRecvCb cb);
+
+    ssize_t recv(int comp_id, unsigned char* buf, size_t len);
+
+    ssize_t send(int comp_id, const unsigned char* buf, size_t len);
+
+    int waitForInitialization(unsigned timeout);
+
+    int waitForNegotiation(unsigned timeout);
+
+    ssize_t waitForData(int comp_id, unsigned int timeout);
+
+    unsigned getComponentCount() const;
+
+private:
+    class Impl;
+    std::unique_ptr<Impl> pimpl_;
 };
 
 class IceTransportFactory {
-    public:
-        IceTransportFactory();
-        ~IceTransportFactory();
-
-        std::shared_ptr<IceTransport> createTransport(const char* name,
-                                                      int component_count,
-                                                      bool master,
-                                                      const IceTransportOptions& options = {});
-
-        /**
-         * PJSIP specifics
-         */
-        pj_ice_strans_cfg getIceCfg() const { return ice_cfg_; }
-        pj_pool_factory* getPoolFactory() { return &cp_.factory; }
-
-    private:
-        pj_caching_pool cp_;
-        std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
-        pj_ice_strans_cfg ice_cfg_;
+public:
+    IceTransportFactory();
+    ~IceTransportFactory();
+
+    std::shared_ptr<IceTransport> createTransport(const char* name,
+                                                  int component_count,
+                                                  bool master,
+                                                  const IceTransportOptions& options = {});
+
+    /**
+     * PJSIP specifics
+     */
+    pj_ice_strans_cfg getIceCfg() const { return ice_cfg_; }
+    pj_pool_factory* getPoolFactory() { return &cp_.factory; }
+
+private:
+    pj_caching_pool cp_;
+    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
+    pj_ice_strans_cfg ice_cfg_;
 };
 
 };
-
-#endif /* ICE_TRANSPORT_H */
-- 
GitLab