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 242 15.7 %
Date: 2026-04-01 09:29:43 Functions: 4 26 15.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          24 :     , 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_DBG("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_DBG(" Received status of PresSubClient %.*s(acc:%s): status=%s note=%s",
     179             :              (int) uri.size(),
     180             :              uri.data(),
     181             :              acc_ID.c_str(),
     182             :              status->info[0].basic_open ? "open" : "closed",
     183             :              note.c_str());
     184             : 
     185           0 :     if (uri == acc_->getFromUri()) {
     186             :         // save the status of our own account
     187           0 :         status_ = status->info[0].basic_open;
     188           0 :         note_ = note;
     189             :     }
     190             :     // report status to client signal
     191           0 :     emitSignal<libjami::PresenceSignal::NewBuddyNotification>(acc_ID,
     192           0 :                                                               std::string(uri),
     193             :                                                               status->info[0].basic_open,
     194             :                                                               note);
     195           0 : }
     196             : 
     197             : void
     198           0 : SIPPresence::subscribeClient(const std::string& uri, bool flag)
     199             : {
     200             :     /* if an account has a server that doesn't support SUBSCRIBE, it's still possible
     201             :      * to subscribe to someone on another server */
     202             :     /*
     203             :     std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen);
     204             :     std::string sub_host = sip_utils::getHostFromUri(uri);
     205             :     if (((not subscribe_supported_) && (account_host == sub_host))
     206             :             or (not enabled_))
     207             :         return;
     208             :     */
     209             : 
     210             :     /* Check if the buddy was already subscribed */
     211           0 :     for (const auto& c : sub_client_list_) {
     212           0 :         if (c->getURI() == uri) {
     213             :             // JAMI_DBG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str());
     214           0 :             if (flag)
     215           0 :                 c->subscribe();
     216             :             else
     217           0 :                 c->unsubscribe();
     218           0 :             return;
     219             :         }
     220             :     }
     221             : 
     222           0 :     if (sub_client_list_.size() >= MAX_N_SUB_CLIENT) {
     223           0 :         JAMI_WARN("Unable to add PresSubClient, max number reached.");
     224           0 :         return;
     225             :     }
     226             : 
     227           0 :     if (flag) {
     228           0 :         PresSubClient* c = new PresSubClient(uri, this);
     229           0 :         if (!(c->subscribe())) {
     230           0 :             JAMI_WARN("Failed send subscribe.");
     231           0 :             delete c;
     232             :         }
     233             :         // the buddy has to be accepted before being added in the list
     234             :     }
     235             : }
     236             : 
     237             : void
     238           0 : SIPPresence::addPresSubClient(PresSubClient* c)
     239             : {
     240           0 :     if (sub_client_list_.size() < MAX_N_SUB_CLIENT) {
     241           0 :         sub_client_list_.push_back(c);
     242           0 :         JAMI_DBG("New Presence_subscription_client added (list[%zu]).", sub_client_list_.size());
     243             :     } else {
     244           0 :         JAMI_WARN("Max Presence_subscription_client is reach.");
     245             :         // let the client alive //delete c;
     246             :     }
     247           0 : }
     248             : 
     249             : void
     250           0 : SIPPresence::removePresSubClient(PresSubClient* c)
     251             : {
     252           0 :     JAMI_DBG("Remove Presence_subscription_client from the buddy list.");
     253           0 :     sub_client_list_.remove(c);
     254           0 : }
     255             : 
     256             : void
     257           0 : SIPPresence::approvePresSubServer(const std::string& uri, bool flag)
     258             : {
     259           0 :     for (const auto& s : sub_server_list_) {
     260           0 :         if (s->matches((char*) uri.c_str())) {
     261           0 :             s->approve(flag);
     262             :             // return; // 'return' would prevent multiple-time subscribers from spam
     263             :         }
     264             :     }
     265           0 : }
     266             : 
     267             : void
     268           0 : SIPPresence::addPresSubServer(PresSubServer* s)
     269             : {
     270           0 :     if (sub_server_list_.size() < MAX_N_SUB_SERVER) {
     271           0 :         sub_server_list_.push_back(s);
     272             :     } else {
     273           0 :         JAMI_WARN("Max Presence_subscription_server is reach.");
     274             :         // let de server alive // delete s;
     275             :     }
     276           0 : }
     277             : 
     278             : void
     279           0 : SIPPresence::removePresSubServer(PresSubServer* s)
     280             : {
     281           0 :     sub_server_list_.remove(s);
     282           0 :     JAMI_DBG("Presence_subscription_server removed");
     283           0 : }
     284             : 
     285             : void
     286           0 : SIPPresence::notifyPresSubServer()
     287             : {
     288           0 :     JAMI_DBG("Iterating through IP2IP Presence_subscription_server:");
     289             : 
     290           0 :     for (const auto& s : sub_server_list_)
     291           0 :         s->notify();
     292           0 : }
     293             : 
     294             : void
     295           0 : SIPPresence::lock()
     296             : {
     297           0 :     mutex_.lock();
     298           0 : }
     299             : 
     300             : bool
     301           0 : SIPPresence::tryLock()
     302             : {
     303           0 :     return mutex_.try_lock();
     304             : }
     305             : 
     306             : void
     307           0 : SIPPresence::unlock()
     308             : {
     309           0 :     mutex_.unlock();
     310           0 : }
     311             : 
     312             : void
     313           0 : SIPPresence::fillDoc(pjsip_tx_data* tdata, const pres_msg_data* msg_data)
     314             : {
     315           0 :     if (tdata->msg->type == PJSIP_REQUEST_MSG) {
     316           0 :         constexpr pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent");
     317           0 :         std::string useragent(acc_->getUserAgentName());
     318           0 :         pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
     319           0 :         pjsip_hdr* h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &pJuseragent);
     320           0 :         pjsip_msg_add_hdr(tdata->msg, h);
     321           0 :     }
     322             : 
     323           0 :     if (msg_data == NULL)
     324           0 :         return;
     325             : 
     326             :     const pjsip_hdr* hdr;
     327           0 :     hdr = msg_data->hdr_list.next;
     328             : 
     329           0 :     while (hdr && hdr != &msg_data->hdr_list) {
     330             :         pjsip_hdr* new_hdr;
     331           0 :         new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
     332           0 :         JAMI_DBG("adding header %p", new_hdr->name.ptr);
     333           0 :         pjsip_msg_add_hdr(tdata->msg, new_hdr);
     334           0 :         hdr = hdr->next;
     335             :     }
     336             : 
     337           0 :     if (msg_data->content_type.slen && msg_data->msg_body.slen) {
     338             :         pjsip_msg_body* body;
     339           0 :         constexpr pj_str_t type = CONST_PJ_STR("application");
     340           0 :         constexpr pj_str_t subtype = CONST_PJ_STR("pidf+xml");
     341           0 :         body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body);
     342           0 :         tdata->msg->body = body;
     343             :     }
     344             : }
     345             : 
     346             : static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request
     347             : 
     348             : /*
     349             :  * Client presence publication callback.
     350             :  */
     351             : void
     352           0 : SIPPresence::publish_cb(struct pjsip_publishc_cbparam* param)
     353             : {
     354           0 :     SIPPresence* pres = (SIPPresence*) param->token;
     355             : 
     356           0 :     if (param->code / 100 != 2 || param->status != PJ_SUCCESS) {
     357           0 :         pjsip_publishc_destroy(param->pubc);
     358           0 :         pres->publish_sess_ = NULL;
     359           0 :         std::string error = fmt::format("{} / {}", param->code, sip_utils::as_view(param->reason));
     360           0 :         if (param->status != PJ_SUCCESS) {
     361             :             char errmsg[PJ_ERR_MSG_SIZE];
     362           0 :             pj_strerror(param->status, errmsg, sizeof(errmsg));
     363           0 :             JAMI_ERR("Client (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg);
     364           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(), error, errmsg);
     365             : 
     366           0 :         } else if (param->code == 412) {
     367             :             /* 412 (Conditional Request Failed)
     368             :              * The PUBLISH refresh has failed, retry with new one.
     369             :              */
     370           0 :             JAMI_WARN("Publish retry.");
     371           0 :             publish(pres);
     372           0 :         } else if ((param->code == PJSIP_SC_BAD_EVENT) || (param->code == PJSIP_SC_NOT_IMPLEMENTED)) { // 489 or 501
     373           0 :             JAMI_WARN("Client (PUBLISH) failed (%s)", error.c_str());
     374             : 
     375           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(),
     376             :                                                              error,
     377             :                                                              "Publish not supported.");
     378             : 
     379           0 :             pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, false);
     380             :         }
     381             : 
     382           0 :     } else {
     383           0 :         if (param->expiration < 1) {
     384             :             /* Could happen if server "forgot" to include Expires header
     385             :              * in the response. We will not renew, so destroy the pubc.
     386             :              */
     387           0 :             pjsip_publishc_destroy(param->pubc);
     388           0 :             pres->publish_sess_ = NULL;
     389             :         }
     390             : 
     391           0 :         pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, true);
     392             :     }
     393           0 : }
     394             : 
     395             : /*
     396             :  * Send PUBLISH request.
     397             :  */
     398             : pj_status_t
     399           0 : SIPPresence::send_publish(SIPPresence* pres)
     400             : {
     401             :     pjsip_tx_data* tdata;
     402             :     pj_status_t status;
     403             : 
     404           0 :     JAMI_DBG("Send PUBLISH (%s).", pres->getAccount()->getAccountID().c_str());
     405             : 
     406           0 :     SIPAccount* acc = pres->getAccount();
     407           0 :     std::string contactWithAngles = acc->getFromUri();
     408           0 :     contactWithAngles.erase(contactWithAngles.find('>'));
     409             : 
     410             :     /* Create PUBLISH request */
     411             :     char* bpos;
     412             :     pj_str_t entity;
     413             : 
     414           0 :     status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata);
     415           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     416             : 
     417           0 :     if (status != PJ_SUCCESS) {
     418           0 :         JAMI_ERR("Error creating PUBLISH request %d", status);
     419           0 :         goto on_error;
     420             :     }
     421             : 
     422           0 :     if ((bpos = pj_strchr(&from, '<')) != NULL) {
     423           0 :         char* epos = pj_strchr(&from, '>');
     424             : 
     425           0 :         if (epos - bpos < 2) {
     426           0 :             JAMI_ERR("Unexpected invalid URI");
     427           0 :             status = PJSIP_EINVALIDURI;
     428           0 :             goto on_error;
     429             :         }
     430             : 
     431           0 :         entity.ptr = bpos + 1;
     432           0 :         entity.slen = epos - bpos - 1;
     433             :     } else {
     434           0 :         entity = from;
     435             :     }
     436             : 
     437             :     /* Create and add PIDF message body */
     438           0 :     status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(), &entity, &tdata->msg->body);
     439             : 
     440             :     pres_msg_data msg_data;
     441             : 
     442           0 :     if (status != PJ_SUCCESS) {
     443           0 :         JAMI_ERR("Error creating PIDF for PUBLISH request");
     444           0 :         pjsip_tx_data_dec_ref(tdata);
     445           0 :         goto on_error;
     446             :     }
     447             : 
     448           0 :     pj_bzero(&msg_data, sizeof(msg_data));
     449           0 :     pj_list_init(&msg_data.hdr_list);
     450           0 :     pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL);
     451           0 :     pj_list_init(&msg_data.multipart_parts);
     452             : 
     453           0 :     pres->fillDoc(tdata, &msg_data);
     454             : 
     455             :     /* Send the PUBLISH request */
     456           0 :     status = pjsip_publishc_send(pres->publish_sess_, tdata);
     457             : 
     458           0 :     if (status == PJ_EPENDING) {
     459           0 :         JAMI_WARN("Previous request is in progress, ");
     460           0 :     } else if (status != PJ_SUCCESS) {
     461           0 :         JAMI_ERR("Error sending PUBLISH request");
     462           0 :         goto on_error;
     463             :     }
     464             : 
     465           0 :     return PJ_SUCCESS;
     466             : 
     467           0 : on_error:
     468             : 
     469           0 :     if (pres->publish_sess_) {
     470           0 :         pjsip_publishc_destroy(pres->publish_sess_);
     471           0 :         pres->publish_sess_ = NULL;
     472             :     }
     473             : 
     474           0 :     return status;
     475           0 : }
     476             : 
     477             : /* Create client publish session */
     478             : pj_status_t
     479           0 : SIPPresence::publish(SIPPresence* pres)
     480             : {
     481             :     pj_status_t status;
     482           0 :     constexpr pj_str_t STR_PRESENCE = CONST_PJ_STR("presence");
     483           0 :     SIPAccount* acc = pres->getAccount();
     484           0 :     pjsip_endpoint* endpt = Manager::instance().sipVoIPLink().getEndpoint();
     485             : 
     486             :     /* Create and init client publication session */
     487             : 
     488             :     /* Create client publication */
     489           0 :     status = pjsip_publishc_create(endpt, &my_publish_opt, pres, &publish_cb, &pres->publish_sess_);
     490             : 
     491           0 :     if (status != PJ_SUCCESS) {
     492           0 :         pres->publish_sess_ = NULL;
     493           0 :         JAMI_ERR("Failed to create a publish session.");
     494           0 :         return status;
     495             :     }
     496             : 
     497             :     /* Initialize client publication */
     498           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     499           0 :     status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF);
     500             : 
     501           0 :     if (status != PJ_SUCCESS) {
     502           0 :         JAMI_ERR("Failed to init a publish session");
     503           0 :         pres->publish_sess_ = NULL;
     504           0 :         return status;
     505             :     }
     506             : 
     507             :     /* Add credential for authentication */
     508           0 :     if (acc->hasCredentials()
     509           0 :         and pjsip_publishc_set_credentials(pres->publish_sess_,
     510           0 :                                            static_cast<int>(acc->getCredentialCount()),
     511             :                                            acc->getCredInfo())
     512             :                 != PJ_SUCCESS) {
     513           0 :         JAMI_ERR("Unable to initialize credentials for invite session authentication");
     514           0 :         return status;
     515             :     }
     516             : 
     517             :     /* Set route-set */
     518             :     // FIXME: is this really necessary?
     519           0 :     pjsip_regc* regc = acc->getRegistrationInfo();
     520           0 :     if (regc and acc->hasServiceRoute())
     521           0 :         pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(acc->getServiceRoute(), pres->getPool()));
     522             : 
     523             :     /* Send initial PUBLISH request */
     524           0 :     status = send_publish(pres);
     525             : 
     526           0 :     if (status != PJ_SUCCESS)
     527           0 :         return status;
     528             : 
     529           0 :     return PJ_SUCCESS;
     530             : }
     531             : 
     532             : } // namespace jami

Generated by: LCOV version 1.14