LCOV - code coverage report
Current view: top level - foo/src/sip - sippresence.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 38 243 15.6 %
Date: 2025-12-18 10:07:43 Functions: 4 26 15.4 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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             : 
      18             : #include "sip/sippresence.h"
      19             : 
      20             : #include "logger.h"
      21             : #include "manager.h"
      22             : #include "sip/sipaccount.h"
      23             : #include "connectivity/sip_utils.h"
      24             : #include "pres_sub_server.h"
      25             : #include "pres_sub_client.h"
      26             : #include "sip/sipvoiplink.h"
      27             : #include "client/ring_signal.h"
      28             : #include "connectivity/sip_utils.h"
      29             : 
      30             : #include <opendht/crypto.h>
      31             : #include <fmt/core.h>
      32             : 
      33             : #include <thread>
      34             : #include <sstream>
      35             : 
      36             : #define MAX_N_SUB_SERVER 50
      37             : #define MAX_N_SUB_CLIENT 50
      38             : 
      39             : namespace jami {
      40             : 
      41             : using sip_utils::CONST_PJ_STR;
      42             : 
      43          24 : SIPPresence::SIPPresence(SIPAccount* acc)
      44          24 :     : publish_sess_()
      45          24 :     , status_data_()
      46          24 :     , enabled_(false)
      47          24 :     , publish_supported_(false)
      48          24 :     , subscribe_supported_(false)
      49          24 :     , status_(false)
      50          24 :     , note_(" ")
      51          24 :     , acc_(acc)
      52          24 :     , sub_server_list_() // IP2IP context
      53          24 :     , sub_client_list_()
      54          24 :     , cp_()
      55          24 :     , pool_()
      56             : {
      57             :     /* init pool */
      58          24 :     pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
      59          24 :     pool_ = pj_pool_create(&cp_.factory, "pres", 1000, 1000, NULL);
      60          24 :     if (!pool_)
      61           0 :         throw std::runtime_error("Unable to allocate pool for presence");
      62             : 
      63             :     /* init default status */
      64          24 :     updateStatus(false, " ");
      65          24 : }
      66             : 
      67          24 : SIPPresence::~SIPPresence()
      68             : {
      69             :     /* Flush the lists */
      70             :     // FIXME: Unable to destroy/unsubscribe buddies properly.
      71             :     // Is the transport usable when the account is being destroyed?
      72             :     // for (const auto & c : sub_client_list_)
      73             :     //    delete(c);
      74          24 :     sub_client_list_.clear();
      75          24 :     sub_server_list_.clear();
      76             : 
      77          24 :     pj_pool_release(pool_);
      78          24 :     pj_caching_pool_destroy(&cp_);
      79          24 : }
      80             : 
      81             : SIPAccount*
      82           0 : SIPPresence::getAccount() const
      83             : {
      84           0 :     return acc_;
      85             : }
      86             : 
      87             : pjsip_pres_status*
      88           0 : SIPPresence::getStatus()
      89             : {
      90           0 :     return &status_data_;
      91             : }
      92             : 
      93             : int
      94           0 : SIPPresence::getModId() const
      95             : {
      96           0 :     return Manager::instance().sipVoIPLink().getModId();
      97             : }
      98             : 
      99             : pj_pool_t*
     100           0 : SIPPresence::getPool() const
     101             : {
     102           0 :     return pool_;
     103             : }
     104             : 
     105             : void
     106          24 : SIPPresence::enable(bool enabled)
     107             : {
     108          24 :     enabled_ = enabled;
     109          24 : }
     110             : 
     111             : void
     112           0 : SIPPresence::support(int function, bool supported)
     113             : {
     114           0 :     if (function == PRESENCE_FUNCTION_PUBLISH)
     115           0 :         publish_supported_ = supported;
     116           0 :     else if (function == PRESENCE_FUNCTION_SUBSCRIBE)
     117           0 :         subscribe_supported_ = supported;
     118           0 : }
     119             : 
     120             : bool
     121           0 : SIPPresence::isSupported(int function)
     122             : {
     123           0 :     if (function == PRESENCE_FUNCTION_PUBLISH)
     124           0 :         return publish_supported_;
     125           0 :     else if (function == PRESENCE_FUNCTION_SUBSCRIBE)
     126           0 :         return subscribe_supported_;
     127             : 
     128           0 :     return false;
     129             : }
     130             : 
     131             : void
     132          24 : SIPPresence::updateStatus(bool status, const std::string& note)
     133             : {
     134             :     // char* pj_note  = (char*) pj_pool_alloc(pool_, "50");
     135             : 
     136          24 :     pjrpid_element rpid = {PJRPID_ELEMENT_TYPE_PERSON, CONST_PJ_STR("0"), PJRPID_ACTIVITY_UNKNOWN, CONST_PJ_STR(note)};
     137             : 
     138             :     /* fill activity if user not available. */
     139          24 :     if (note == "away")
     140           0 :         rpid.activity = PJRPID_ACTIVITY_AWAY;
     141          24 :     else if (note == "busy")
     142           0 :         rpid.activity = PJRPID_ACTIVITY_BUSY;
     143             :     /*
     144             :     else // TODO: is there any other possibilities
     145             :         JAMI_DBG("Presence : no activity");
     146             :     */
     147             : 
     148          24 :     pj_bzero(&status_data_, sizeof(status_data_));
     149          24 :     status_data_.info_cnt = 1;
     150          24 :     status_data_.info[0].basic_open = status;
     151             : 
     152             :     // at most we will have 3 digits + NULL termination
     153             :     char buf[4];
     154          24 :     pj_utoa(rand() % 1000, buf);
     155          24 :     status_data_.info[0].id = pj_strdup3(pool_, buf);
     156             : 
     157          24 :     pj_memcpy(&status_data_.info[0].rpid, &rpid, sizeof(pjrpid_element));
     158             :     /* "contact" field is optionnal */
     159          24 : }
     160             : 
     161             : void
     162           0 : SIPPresence::sendPresence(bool status, const std::string& note)
     163             : {
     164           0 :     updateStatus(status, note);
     165             : 
     166             :     // if ((not publish_supported_) or (not enabled_))
     167             :     //    return;
     168             : 
     169           0 :     if (acc_->isIP2IP())
     170           0 :         notifyPresSubServer(); // to each subscribers
     171             :     else
     172           0 :         publish(this); // to the PBX server
     173           0 : }
     174             : 
     175             : void
     176           0 : SIPPresence::reportPresSubClientNotification(std::string_view uri, pjsip_pres_status* status)
     177             : {
     178             :     /* Update our info. See pjsua_buddy_get_info() for additionnal ideas*/
     179           0 :     const std::string& acc_ID = acc_->getAccountID();
     180           0 :     const std::string note(status->info[0].rpid.note.ptr, status->info[0].rpid.note.slen);
     181           0 :     JAMI_DBG(" Received status of PresSubClient %.*s(acc:%s): status=%s note=%s",
     182             :              (int) uri.size(),
     183             :              uri.data(),
     184             :              acc_ID.c_str(),
     185             :              status->info[0].basic_open ? "open" : "closed",
     186             :              note.c_str());
     187             : 
     188           0 :     if (uri == acc_->getFromUri()) {
     189             :         // save the status of our own account
     190           0 :         status_ = status->info[0].basic_open;
     191           0 :         note_ = note;
     192             :     }
     193             :     // report status to client signal
     194           0 :     emitSignal<libjami::PresenceSignal::NewBuddyNotification>(acc_ID,
     195           0 :                                                               std::string(uri),
     196             :                                                               status->info[0].basic_open,
     197             :                                                               note);
     198           0 : }
     199             : 
     200             : void
     201           0 : SIPPresence::subscribeClient(const std::string& uri, bool flag)
     202             : {
     203             :     /* if an account has a server that doesn't support SUBSCRIBE, it's still possible
     204             :      * to subscribe to someone on another server */
     205             :     /*
     206             :     std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen);
     207             :     std::string sub_host = sip_utils::getHostFromUri(uri);
     208             :     if (((not subscribe_supported_) && (account_host == sub_host))
     209             :             or (not enabled_))
     210             :         return;
     211             :     */
     212             : 
     213             :     /* Check if the buddy was already subscribed */
     214           0 :     for (const auto& c : sub_client_list_) {
     215           0 :         if (c->getURI() == uri) {
     216             :             // JAMI_DBG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str());
     217           0 :             if (flag)
     218           0 :                 c->subscribe();
     219             :             else
     220           0 :                 c->unsubscribe();
     221           0 :             return;
     222             :         }
     223             :     }
     224             : 
     225           0 :     if (sub_client_list_.size() >= MAX_N_SUB_CLIENT) {
     226           0 :         JAMI_WARN("Unable to add PresSubClient, max number reached.");
     227           0 :         return;
     228             :     }
     229             : 
     230           0 :     if (flag) {
     231           0 :         PresSubClient* c = new PresSubClient(uri, this);
     232           0 :         if (!(c->subscribe())) {
     233           0 :             JAMI_WARN("Failed send subscribe.");
     234           0 :             delete c;
     235             :         }
     236             :         // the buddy has to be accepted before being added in the list
     237             :     }
     238             : }
     239             : 
     240             : void
     241           0 : SIPPresence::addPresSubClient(PresSubClient* c)
     242             : {
     243           0 :     if (sub_client_list_.size() < MAX_N_SUB_CLIENT) {
     244           0 :         sub_client_list_.push_back(c);
     245           0 :         JAMI_DBG("New Presence_subscription_client added (list[%zu]).", sub_client_list_.size());
     246             :     } else {
     247           0 :         JAMI_WARN("Max Presence_subscription_client is reach.");
     248             :         // let the client alive //delete c;
     249             :     }
     250           0 : }
     251             : 
     252             : void
     253           0 : SIPPresence::removePresSubClient(PresSubClient* c)
     254             : {
     255           0 :     JAMI_DBG("Remove Presence_subscription_client from the buddy list.");
     256           0 :     sub_client_list_.remove(c);
     257           0 : }
     258             : 
     259             : void
     260           0 : SIPPresence::approvePresSubServer(const std::string& uri, bool flag)
     261             : {
     262           0 :     for (const auto& s : sub_server_list_) {
     263           0 :         if (s->matches((char*) uri.c_str())) {
     264           0 :             s->approve(flag);
     265             :             // return; // 'return' would prevent multiple-time subscribers from spam
     266             :         }
     267             :     }
     268           0 : }
     269             : 
     270             : void
     271           0 : SIPPresence::addPresSubServer(PresSubServer* s)
     272             : {
     273           0 :     if (sub_server_list_.size() < MAX_N_SUB_SERVER) {
     274           0 :         sub_server_list_.push_back(s);
     275             :     } else {
     276           0 :         JAMI_WARN("Max Presence_subscription_server is reach.");
     277             :         // let de server alive // delete s;
     278             :     }
     279           0 : }
     280             : 
     281             : void
     282           0 : SIPPresence::removePresSubServer(PresSubServer* s)
     283             : {
     284           0 :     sub_server_list_.remove(s);
     285           0 :     JAMI_DBG("Presence_subscription_server removed");
     286           0 : }
     287             : 
     288             : void
     289           0 : SIPPresence::notifyPresSubServer()
     290             : {
     291           0 :     JAMI_DBG("Iterating through IP2IP Presence_subscription_server:");
     292             : 
     293           0 :     for (const auto& s : sub_server_list_)
     294           0 :         s->notify();
     295           0 : }
     296             : 
     297             : void
     298           0 : SIPPresence::lock()
     299             : {
     300           0 :     mutex_.lock();
     301           0 : }
     302             : 
     303             : bool
     304           0 : SIPPresence::tryLock()
     305             : {
     306           0 :     return mutex_.try_lock();
     307             : }
     308             : 
     309             : void
     310           0 : SIPPresence::unlock()
     311             : {
     312           0 :     mutex_.unlock();
     313           0 : }
     314             : 
     315             : void
     316           0 : SIPPresence::fillDoc(pjsip_tx_data* tdata, const pres_msg_data* msg_data)
     317             : {
     318           0 :     if (tdata->msg->type == PJSIP_REQUEST_MSG) {
     319           0 :         constexpr pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent");
     320           0 :         std::string useragent(acc_->getUserAgentName());
     321           0 :         pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
     322           0 :         pjsip_hdr* h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &pJuseragent);
     323           0 :         pjsip_msg_add_hdr(tdata->msg, h);
     324           0 :     }
     325             : 
     326           0 :     if (msg_data == NULL)
     327           0 :         return;
     328             : 
     329             :     const pjsip_hdr* hdr;
     330           0 :     hdr = msg_data->hdr_list.next;
     331             : 
     332           0 :     while (hdr && hdr != &msg_data->hdr_list) {
     333             :         pjsip_hdr* new_hdr;
     334           0 :         new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
     335           0 :         JAMI_DBG("adding header %p", new_hdr->name.ptr);
     336           0 :         pjsip_msg_add_hdr(tdata->msg, new_hdr);
     337           0 :         hdr = hdr->next;
     338             :     }
     339             : 
     340           0 :     if (msg_data->content_type.slen && msg_data->msg_body.slen) {
     341             :         pjsip_msg_body* body;
     342           0 :         constexpr pj_str_t type = CONST_PJ_STR("application");
     343           0 :         constexpr pj_str_t subtype = CONST_PJ_STR("pidf+xml");
     344           0 :         body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body);
     345           0 :         tdata->msg->body = body;
     346             :     }
     347             : }
     348             : 
     349             : static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request
     350             : 
     351             : /*
     352             :  * Client presence publication callback.
     353             :  */
     354             : void
     355           0 : SIPPresence::publish_cb(struct pjsip_publishc_cbparam* param)
     356             : {
     357           0 :     SIPPresence* pres = (SIPPresence*) param->token;
     358             : 
     359           0 :     if (param->code / 100 != 2 || param->status != PJ_SUCCESS) {
     360           0 :         pjsip_publishc_destroy(param->pubc);
     361           0 :         pres->publish_sess_ = NULL;
     362           0 :         std::string error = fmt::format("{} / {}", param->code, sip_utils::as_view(param->reason));
     363           0 :         if (param->status != PJ_SUCCESS) {
     364             :             char errmsg[PJ_ERR_MSG_SIZE];
     365           0 :             pj_strerror(param->status, errmsg, sizeof(errmsg));
     366           0 :             JAMI_ERR("Client (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg);
     367           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(), error, errmsg);
     368             : 
     369           0 :         } else if (param->code == 412) {
     370             :             /* 412 (Conditional Request Failed)
     371             :              * The PUBLISH refresh has failed, retry with new one.
     372             :              */
     373           0 :             JAMI_WARN("Publish retry.");
     374           0 :             publish(pres);
     375           0 :         } else if ((param->code == PJSIP_SC_BAD_EVENT) || (param->code == PJSIP_SC_NOT_IMPLEMENTED)) { // 489 or 501
     376           0 :             JAMI_WARN("Client (PUBLISH) failed (%s)", error.c_str());
     377             : 
     378           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(),
     379             :                                                              error,
     380             :                                                              "Publish not supported.");
     381             : 
     382           0 :             pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, false);
     383             :         }
     384             : 
     385           0 :     } else {
     386           0 :         if (param->expiration < 1) {
     387             :             /* Could happen if server "forgot" to include Expires header
     388             :              * in the response. We will not renew, so destroy the pubc.
     389             :              */
     390           0 :             pjsip_publishc_destroy(param->pubc);
     391           0 :             pres->publish_sess_ = NULL;
     392             :         }
     393             : 
     394           0 :         pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, true);
     395             :     }
     396           0 : }
     397             : 
     398             : /*
     399             :  * Send PUBLISH request.
     400             :  */
     401             : pj_status_t
     402           0 : SIPPresence::send_publish(SIPPresence* pres)
     403             : {
     404             :     pjsip_tx_data* tdata;
     405             :     pj_status_t status;
     406             : 
     407           0 :     JAMI_DBG("Send PUBLISH (%s).", pres->getAccount()->getAccountID().c_str());
     408             : 
     409           0 :     SIPAccount* acc = pres->getAccount();
     410           0 :     std::string contactWithAngles = acc->getFromUri();
     411           0 :     contactWithAngles.erase(contactWithAngles.find('>'));
     412           0 :     int semicolon = contactWithAngles.find_first_of(':');
     413           0 :     std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1);
     414             :     //    pj_str_t contact = pj_str(strdup(contactWithoutAngles.c_str()));
     415             :     //    pj_memcpy(&status_data.info[0].contact, &contt, sizeof(pj_str_t));;
     416             : 
     417             :     /* Create PUBLISH request */
     418             :     char* bpos;
     419             :     pj_str_t entity;
     420             : 
     421           0 :     status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata);
     422           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     423             : 
     424           0 :     if (status != PJ_SUCCESS) {
     425           0 :         JAMI_ERR("Error creating PUBLISH request %d", status);
     426           0 :         goto on_error;
     427             :     }
     428             : 
     429           0 :     if ((bpos = pj_strchr(&from, '<')) != NULL) {
     430           0 :         char* epos = pj_strchr(&from, '>');
     431             : 
     432           0 :         if (epos - bpos < 2) {
     433           0 :             JAMI_ERR("Unexpected invalid URI");
     434           0 :             status = PJSIP_EINVALIDURI;
     435           0 :             goto on_error;
     436             :         }
     437             : 
     438           0 :         entity.ptr = bpos + 1;
     439           0 :         entity.slen = epos - bpos - 1;
     440             :     } else {
     441           0 :         entity = from;
     442             :     }
     443             : 
     444             :     /* Create and add PIDF message body */
     445           0 :     status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(), &entity, &tdata->msg->body);
     446             : 
     447             :     pres_msg_data msg_data;
     448             : 
     449           0 :     if (status != PJ_SUCCESS) {
     450           0 :         JAMI_ERR("Error creating PIDF for PUBLISH request");
     451           0 :         pjsip_tx_data_dec_ref(tdata);
     452           0 :         goto on_error;
     453             :     }
     454             : 
     455           0 :     pj_bzero(&msg_data, sizeof(msg_data));
     456           0 :     pj_list_init(&msg_data.hdr_list);
     457           0 :     pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL);
     458           0 :     pj_list_init(&msg_data.multipart_parts);
     459             : 
     460           0 :     pres->fillDoc(tdata, &msg_data);
     461             : 
     462             :     /* Send the PUBLISH request */
     463           0 :     status = pjsip_publishc_send(pres->publish_sess_, tdata);
     464             : 
     465           0 :     if (status == PJ_EPENDING) {
     466           0 :         JAMI_WARN("Previous request is in progress, ");
     467           0 :     } else if (status != PJ_SUCCESS) {
     468           0 :         JAMI_ERR("Error sending PUBLISH request");
     469           0 :         goto on_error;
     470             :     }
     471             : 
     472           0 :     return PJ_SUCCESS;
     473             : 
     474           0 : on_error:
     475             : 
     476           0 :     if (pres->publish_sess_) {
     477           0 :         pjsip_publishc_destroy(pres->publish_sess_);
     478           0 :         pres->publish_sess_ = NULL;
     479             :     }
     480             : 
     481           0 :     return status;
     482           0 : }
     483             : 
     484             : /* Create client publish session */
     485             : pj_status_t
     486           0 : SIPPresence::publish(SIPPresence* pres)
     487             : {
     488             :     pj_status_t status;
     489           0 :     constexpr pj_str_t STR_PRESENCE = CONST_PJ_STR("presence");
     490           0 :     SIPAccount* acc = pres->getAccount();
     491           0 :     pjsip_endpoint* endpt = Manager::instance().sipVoIPLink().getEndpoint();
     492             : 
     493             :     /* Create and init client publication session */
     494             : 
     495             :     /* Create client publication */
     496           0 :     status = pjsip_publishc_create(endpt, &my_publish_opt, pres, &publish_cb, &pres->publish_sess_);
     497             : 
     498           0 :     if (status != PJ_SUCCESS) {
     499           0 :         pres->publish_sess_ = NULL;
     500           0 :         JAMI_ERR("Failed to create a publish session.");
     501           0 :         return status;
     502             :     }
     503             : 
     504             :     /* Initialize client publication */
     505           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     506           0 :     status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF);
     507             : 
     508           0 :     if (status != PJ_SUCCESS) {
     509           0 :         JAMI_ERR("Failed to init a publish session");
     510           0 :         pres->publish_sess_ = NULL;
     511           0 :         return status;
     512             :     }
     513             : 
     514             :     /* Add credential for authentication */
     515           0 :     if (acc->hasCredentials()
     516           0 :         and pjsip_publishc_set_credentials(pres->publish_sess_, acc->getCredentialCount(), acc->getCredInfo())
     517             :                 != PJ_SUCCESS) {
     518           0 :         JAMI_ERR("Unable to initialize credentials for invite session authentication");
     519           0 :         return status;
     520             :     }
     521             : 
     522             :     /* Set route-set */
     523             :     // FIXME: is this really necessary?
     524           0 :     pjsip_regc* regc = acc->getRegistrationInfo();
     525           0 :     if (regc and acc->hasServiceRoute())
     526           0 :         pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(acc->getServiceRoute(), pres->getPool()));
     527             : 
     528             :     /* Send initial PUBLISH request */
     529           0 :     status = send_publish(pres);
     530             : 
     531           0 :     if (status != PJ_SUCCESS)
     532           0 :         return status;
     533             : 
     534           0 :     return PJ_SUCCESS;
     535             : }
     536             : 
     537             : } // namespace jami

Generated by: LCOV version 1.14