add initial project structure

Change-Id: I6a3fb080ff623b312e42d71754480a7ce00b81a0
diff --git a/src/upnp/upnp_control.cpp b/src/upnp/upnp_control.cpp
new file mode 100644
index 0000000..b255617
--- /dev/null
+++ b/src/upnp/upnp_control.cpp
@@ -0,0 +1,150 @@
+/*
+ *  Copyright (C) 2004-2023 Savoir-faire Linux Inc.
+ *
+ *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
+ *  Author: Eden Abitbol <eden.abitbol@savoirfairelinux.com>
+ *  Author: Mohamed Chibani <mohamed.chibani@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 "upnp_control.h"
+
+namespace jami {
+namespace upnp {
+
+Controller::Controller()
+{
+    try {
+        upnpContext_ = UPnPContext::getUPnPContext();
+    } catch (std::runtime_error& e) {
+        JAMI_ERR("UPnP context error: %s", e.what());
+    }
+
+    assert(upnpContext_);
+    upnpContext_->registerController(this);
+
+    JAMI_DBG("Controller@%p: Created UPnP Controller session", this);
+}
+
+Controller::~Controller()
+{
+    JAMI_DBG("Controller@%p: Destroying UPnP Controller session", this);
+
+    releaseAllMappings();
+    upnpContext_->unregisterController(this);
+}
+
+void
+Controller::setPublicAddress(const IpAddr& addr)
+{
+    assert(upnpContext_);
+
+    if (addr and addr.getFamily() == AF_INET) {
+        upnpContext_->setPublicAddress(addr);
+    }
+}
+
+bool
+Controller::isReady() const
+{
+    assert(upnpContext_);
+    return upnpContext_->isReady();
+}
+
+IpAddr
+Controller::getExternalIP() const
+{
+    assert(upnpContext_);
+    if (upnpContext_->isReady()) {
+        return upnpContext_->getExternalIP();
+    }
+    return {};
+}
+
+Mapping::sharedPtr_t
+Controller::reserveMapping(uint16_t port, PortType type)
+{
+    Mapping map(type, port, port);
+    return reserveMapping(map);
+}
+
+Mapping::sharedPtr_t
+Controller::reserveMapping(Mapping& requestedMap)
+{
+    assert(upnpContext_);
+
+    // Try to get a provisioned port
+    auto mapRes = upnpContext_->reserveMapping(requestedMap);
+    if (mapRes)
+        addLocalMap(*mapRes);
+    return mapRes;
+}
+
+void
+Controller::releaseMapping(const Mapping& map)
+{
+    assert(upnpContext_);
+
+    removeLocalMap(map);
+    return upnpContext_->releaseMapping(map);
+}
+
+void
+Controller::releaseAllMappings()
+{
+    assert(upnpContext_);
+
+    std::lock_guard<std::mutex> lk(mapListMutex_);
+    for (auto const& [_, map] : mappingList_) {
+        upnpContext_->releaseMapping(map);
+    }
+    mappingList_.clear();
+}
+
+void
+Controller::addLocalMap(const Mapping& map)
+{
+    if (map.getMapKey()) {
+        std::lock_guard<std::mutex> lock(mapListMutex_);
+        auto ret = mappingList_.emplace(map.getMapKey(), map);
+        if (not ret.second) {
+            JAMI_WARN("Mapping request for %s already in the list!", map.toString().c_str());
+        }
+    }
+}
+
+bool
+Controller::removeLocalMap(const Mapping& map)
+{
+    assert(upnpContext_);
+
+    std::lock_guard<std::mutex> lk(mapListMutex_);
+    if (mappingList_.erase(map.getMapKey()) != 1) {
+        JAMI_ERR("Failed to remove mapping %s from local list", map.getTypeStr());
+        return false;
+    }
+
+    return true;
+}
+
+uint16_t
+Controller::generateRandomPort(PortType type)
+{
+    return UPnPContext::generateRandomPort(type);
+}
+
+} // namespace upnp
+} // namespace jami