LCOV - code coverage report
Current view: top level - src/jamidht - service_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 11.5 % 200 23
Test Date: 2026-06-13 09:18:46 Functions: 13.8 % 29 4

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : #include "jamidht/service_manager.h"
      18              : 
      19              : #include "fileutils.h"
      20              : #include "json_utils.h"
      21              : #include "logger.h"
      22              : 
      23              : #include <algorithm>
      24              : #include <fstream>
      25              : #include <random>
      26              : #include <system_error>
      27              : #include <string_view>
      28              : 
      29              : namespace jami {
      30              : 
      31              : namespace {
      32              : 
      33              : using namespace std::literals;
      34              : 
      35              : constexpr std::string_view SERVICES_FILENAME = "exposed_services.json";
      36              : 
      37              : std::string
      38            0 : policyToString(AccessPolicy p)
      39              : {
      40            0 :     switch (p) {
      41            0 :     case AccessPolicy::CONTACTS_ONLY:
      42            0 :         return "contacts"s;
      43            0 :     case AccessPolicy::SPECIFIC_CONTACTS:
      44            0 :         return "specific"s;
      45            0 :     case AccessPolicy::PUBLIC:
      46            0 :         return "public"s;
      47              :     }
      48            0 :     return "contacts"s;
      49              : }
      50              : 
      51              : constexpr AccessPolicy
      52            0 : policyFromString(std::string_view s)
      53              : {
      54            0 :     if (s == "specific"sv)
      55            0 :         return AccessPolicy::SPECIFIC_CONTACTS;
      56            0 :     if (s == "public"sv)
      57            0 :         return AccessPolicy::PUBLIC;
      58            0 :     return AccessPolicy::CONTACTS_ONLY;
      59              : }
      60              : 
      61              : Json::Value
      62            0 : toJson(const ServiceRecord& r)
      63              : {
      64            0 :     Json::Value v(Json::objectValue);
      65            0 :     v["id"] = r.id;
      66            0 :     v["type"] = r.type;
      67            0 :     v["name"] = r.name;
      68            0 :     v["description"] = r.description;
      69            0 :     v["scheme"] = r.scheme;
      70            0 :     v["localHost"] = r.localHost;
      71            0 :     v["localPort"] = static_cast<Json::UInt>(r.localPort);
      72            0 :     v["directory"] = r.directory;
      73            0 :     v["policy"] = policyToString(r.policy);
      74            0 :     Json::Value allowed(Json::arrayValue);
      75            0 :     for (const auto& a : r.allowedContacts)
      76            0 :         allowed.append(a);
      77            0 :     v["allowedContacts"] = std::move(allowed);
      78            0 :     v["enabled"] = r.enabled;
      79            0 :     return v;
      80            0 : }
      81              : 
      82              : bool
      83            0 : fromJson(const Json::Value& v, ServiceRecord& r)
      84              : {
      85            0 :     if (!v.isObject())
      86            0 :         return false;
      87            0 :     r.id = v.get("id", "").asString();
      88            0 :     r.type = v.get("type", "custom").asString();
      89            0 :     if (r.type.empty())
      90            0 :         r.type = "custom";
      91            0 :     r.name = v.get("name", "").asString();
      92            0 :     r.description = v.get("description", "").asString();
      93            0 :     r.scheme = v.get("scheme", "").asString();
      94            0 :     r.localHost = v.get("localHost", "localhost").asString();
      95            0 :     r.localPort = static_cast<uint16_t>(v.get("localPort", 0).asUInt());
      96            0 :     r.directory = v.get("directory", "").asString();
      97            0 :     r.policy = policyFromString(v.get("policy", "contacts").asString());
      98            0 :     r.allowedContacts.clear();
      99            0 :     if (v.isMember("allowedContacts") && v["allowedContacts"].isArray()) {
     100            0 :         for (const auto& a : v["allowedContacts"])
     101            0 :             r.allowedContacts.push_back(a.asString());
     102              :     }
     103            0 :     r.enabled = v.get("enabled", true).asBool();
     104            0 :     return !r.id.empty();
     105              : }
     106              : 
     107              : } // namespace
     108              : 
     109              : std::string
     110            0 : generateServiceUuid(std::mt19937_64& rng)
     111              : {
     112              :     // RFC 4122 v4 UUID.
     113            0 :     std::uniform_int_distribution<uint64_t> dist;
     114            0 :     uint64_t a = dist(rng);
     115            0 :     uint64_t b = dist(rng);
     116              :     // Set version (0100) and variant (10).
     117            0 :     a = (a & 0xffffffffffff0fffULL) | 0x0000000000004000ULL;
     118            0 :     b = (b & 0x3fffffffffffffffULL) | 0x8000000000000000ULL;
     119              :     return fmt::format("{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
     120            0 :                        static_cast<uint32_t>((a >> 32) & 0xffffffffULL),
     121            0 :                        static_cast<uint32_t>((a >> 16) & 0xffffULL),
     122            0 :                        static_cast<uint32_t>(a & 0xffffULL),
     123            0 :                        static_cast<uint32_t>((b >> 48) & 0xffffULL),
     124            0 :                        static_cast<uint64_t>(b & 0xffffffffffffULL));
     125              : }
     126              : 
     127          661 : ServiceManager::ServiceManager(std::filesystem::path storagePath)
     128          661 :     : storagePath_(std::move(storagePath))
     129              : {
     130          661 :     std::unique_lock lk(mutex_);
     131          661 :     loadLocked();
     132          661 : }
     133              : 
     134              : std::filesystem::path
     135            0 : ServiceManager::filePath() const
     136              : {
     137            0 :     return storagePath_ / SERVICES_FILENAME;
     138              : }
     139              : 
     140              : void
     141          661 : ServiceManager::loadLocked()
     142              : {
     143          661 :     services_.clear();
     144          661 :     auto path = storagePath_ / SERVICES_FILENAME;
     145          661 :     std::error_code ec;
     146          661 :     if (!std::filesystem::exists(path, ec))
     147          661 :         return;
     148            0 :     std::ifstream in(path);
     149            0 :     if (!in)
     150            0 :         return;
     151            0 :     std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
     152            0 :     Json::Value root;
     153            0 :     if (!json::parse(content, root) || !root.isArray())
     154            0 :         return;
     155            0 :     for (const auto& v : root) {
     156            0 :         ServiceRecord r;
     157            0 :         if (fromJson(v, r))
     158            0 :             services_.emplace(r.id, std::move(r));
     159            0 :     }
     160          661 : }
     161              : 
     162              : void
     163            0 : ServiceManager::saveLocked() const
     164              : {
     165            0 :     std::error_code ec;
     166            0 :     std::filesystem::create_directories(storagePath_, ec);
     167            0 :     Json::Value root(Json::arrayValue);
     168            0 :     for (const auto& [_id, r] : services_)
     169            0 :         root.append(toJson(r));
     170            0 :     auto path = storagePath_ / SERVICES_FILENAME;
     171            0 :     std::ofstream out(path, std::ios::trunc);
     172            0 :     if (!out) {
     173            0 :         JAMI_WARNING("[ServiceManager] Unable to write {}", path.string());
     174            0 :         return;
     175              :     }
     176            0 :     out << json::toString(root);
     177            0 : }
     178              : 
     179              : std::string
     180            0 : ServiceManager::addService(ServiceRecord rec, std::mt19937_64& rng)
     181              : {
     182            0 :     if (rec.name.empty() || rec.localPort == 0)
     183            0 :         return {};
     184            0 :     if (rec.id.empty())
     185            0 :         rec.id = generateServiceUuid(rng);
     186            0 :     std::unique_lock lk(mutex_);
     187            0 :     auto id = rec.id;
     188            0 :     services_[id] = std::move(rec);
     189            0 :     saveLocked();
     190            0 :     const auto& stored = services_[id];
     191            0 :     JAMI_LOG("[ServiceManager] added service id={} name=\"{}\" target={}:{} enabled={}",
     192              :              id,
     193              :              stored.name,
     194              :              stored.localHost,
     195              :              stored.localPort,
     196              :              stored.enabled);
     197            0 :     lk.unlock();
     198            0 :     notifyChanged();
     199            0 :     return id;
     200            0 : }
     201              : 
     202              : bool
     203            0 : ServiceManager::updateService(const ServiceRecord& rec)
     204              : {
     205            0 :     if (rec.id.empty() || rec.name.empty() || rec.localPort == 0)
     206            0 :         return false;
     207            0 :     std::unique_lock lk(mutex_);
     208            0 :     auto it = services_.find(rec.id);
     209            0 :     if (it == services_.end())
     210            0 :         return false;
     211            0 :     bool wasEnabled = it->second.enabled;
     212            0 :     it->second = rec;
     213            0 :     saveLocked();
     214            0 :     if (wasEnabled != rec.enabled)
     215            0 :         JAMI_LOG("[ServiceManager] service id={} name=\"{}\" {}",
     216              :                  rec.id,
     217              :                  rec.name,
     218              :                  rec.enabled ? "enabled" : "disabled");
     219              :     else
     220            0 :         JAMI_LOG("[ServiceManager] updated service id={} name=\"{}\" target={}:{}",
     221              :                  rec.id,
     222              :                  rec.name,
     223              :                  rec.localHost,
     224              :                  rec.localPort);
     225            0 :     lk.unlock();
     226            0 :     notifyChanged();
     227            0 :     return true;
     228            0 : }
     229              : 
     230              : bool
     231            0 : ServiceManager::removeService(const std::string& id)
     232              : {
     233            0 :     std::unique_lock lk(mutex_);
     234            0 :     auto erased = services_.erase(id) > 0;
     235            0 :     if (erased) {
     236            0 :         saveLocked();
     237            0 :         JAMI_LOG("[ServiceManager] removed service id={}", id);
     238            0 :         lk.unlock();
     239            0 :         notifyChanged();
     240              :     }
     241            0 :     return erased;
     242            0 : }
     243              : 
     244              : std::vector<ServiceRecord>
     245            0 : ServiceManager::getServices() const
     246              : {
     247            0 :     std::shared_lock lk(mutex_);
     248            0 :     std::vector<ServiceRecord> out;
     249            0 :     out.reserve(services_.size());
     250            0 :     for (const auto& [_id, r] : services_)
     251            0 :         out.push_back(r);
     252            0 :     return out;
     253            0 : }
     254              : 
     255              : std::optional<ServiceRecord>
     256            0 : ServiceManager::getService(const std::string& id) const
     257              : {
     258            0 :     std::shared_lock lk(mutex_);
     259            0 :     auto it = services_.find(id);
     260            0 :     if (it == services_.end())
     261            0 :         return std::nullopt;
     262            0 :     return it->second;
     263            0 : }
     264              : 
     265              : bool
     266            0 : ServiceManager::isAuthorizedNoLock(const ServiceRecord& rec,
     267              :                                    const std::string& peerAccountUri,
     268              :                                    const ContactChecker& isContact)
     269              : {
     270            0 :     if (!rec.enabled)
     271            0 :         return false;
     272            0 :     switch (rec.policy) {
     273            0 :     case AccessPolicy::PUBLIC:
     274            0 :         return true;
     275            0 :     case AccessPolicy::CONTACTS_ONLY:
     276            0 :         return isContact && isContact(peerAccountUri);
     277            0 :     case AccessPolicy::SPECIFIC_CONTACTS:
     278            0 :         return std::find(rec.allowedContacts.begin(), rec.allowedContacts.end(), peerAccountUri)
     279            0 :                != rec.allowedContacts.end();
     280              :     }
     281            0 :     return false;
     282              : }
     283              : 
     284              : bool
     285            0 : ServiceManager::isAuthorized(const std::string& serviceId,
     286              :                              const std::string& peerAccountUri,
     287              :                              const ContactChecker& isContact) const
     288              : {
     289            0 :     std::shared_lock lk(mutex_);
     290            0 :     auto it = services_.find(serviceId);
     291            0 :     if (it == services_.end())
     292            0 :         return false;
     293            0 :     return isAuthorizedNoLock(it->second, peerAccountUri, isContact);
     294            0 : }
     295              : 
     296              : std::vector<ServiceRecord>
     297         1213 : ServiceManager::getVisibleServices(const std::string& peerAccountUri, const ContactChecker& isContact) const
     298              : {
     299         1213 :     std::shared_lock lk(mutex_);
     300         1213 :     std::vector<ServiceRecord> out;
     301         1213 :     out.reserve(services_.size());
     302         1213 :     for (const auto& [_id, r] : services_) {
     303            0 :         if (isAuthorizedNoLock(r, peerAccountUri, isContact))
     304            0 :             out.push_back(r);
     305              :     }
     306         2426 :     return out;
     307         1213 : }
     308              : 
     309              : void
     310          672 : ServiceManager::setOnChanged(OnChangeCb cb)
     311              : {
     312          672 :     std::unique_lock lk(mutex_);
     313          672 :     onChangeCb_ = std::move(cb);
     314          672 : }
     315              : 
     316              : void
     317            0 : ServiceManager::notifyChanged()
     318              : {
     319            0 :     OnChangeCb cb;
     320              :     {
     321            0 :         std::shared_lock lk(mutex_);
     322            0 :         cb = onChangeCb_;
     323            0 :     }
     324            0 :     if (cb)
     325            0 :         cb();
     326            0 : }
     327              : 
     328              : } // namespace jami
        

Generated by: LCOV version 2.0-1