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

Generated by: LCOV version 2.0-1