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