Ring Daemon
Loading...
Searching...
No Matches
pres_sub_client.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2012, 2013 LOTES TM LLC
3 * Author : Andrey Loukhnov <aol.nnov@gmail.com>
4 *
5 * This file is a part of pult5-voip
6 *
7 * pult5-voip is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * pult5-voip is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this programm. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <pj/log.h>
22#include <pj/rand.h>
23#include <pjsip/sip_module.h>
24#include <pjsip/sip_types.h>
25#include <pjsip/sip_event.h>
26#include <pjsip/sip_transaction.h>
27#include <pjsip/sip_dialog.h>
28#include <pjsip/sip_endpoint.h>
29#include <string>
30#include <sstream>
31#include <thread>
32#include <pj/pool.h>
33#include <pjsip/sip_ua_layer.h>
34#include <pjsip-simple/evsub.h>
35#include <unistd.h>
36
37#include "pres_sub_client.h"
38#include "sip/sipaccount.h"
39#include "sip/sippresence.h"
40#include "sip/sipvoiplink.h"
42#include "manager.h"
43#include "client/jami_signal.h"
44
45#include "logger.h"
46
47#define PRES_TIMER 300 // 5min
48
49namespace jami {
50
52
53int PresSubClient::modId_ = 0; // used to extract data structure from event_subscription
54
55void
56PresSubClient::pres_client_timer_cb(pj_timer_heap_t* /*th*/, pj_timer_entry* entry)
57{
58 PresSubClient* c = (PresSubClient*) entry->user_data;
59 JAMI_DBG("timeout for %.*s", (int) c->getURI().size(), c->getURI().data());
60}
61
62/* Callback called when *client* subscription state has changed. */
63void
64PresSubClient::pres_client_evsub_on_state(pjsip_evsub* sub, pjsip_event* event)
65{
67
68 PresSubClient* pres_client = (PresSubClient*) pjsip_evsub_get_mod_data(sub, modId_);
69 /* No need to pres->lock() here since the client has a locked dialog*/
70
71 if (!pres_client) {
72 JAMI_WARN("pres_client not found");
73 return;
74 }
75
76 JAMI_DBG("Subscription for pres_client '%.*s' is '%s'",
77 (int) pres_client->getURI().size(),
78 pres_client->getURI().data(),
80
82
83 SIPPresence* pres = pres_client->getPresence();
84
85 if (state == PJSIP_EVSUB_STATE_ACCEPTED) {
86 pres_client->enable(true);
88 std::string(pres_client->getURI()),
89 PJ_TRUE);
90
91 pres->getAccount()->supportPresence(PRESENCE_FUNCTION_SUBSCRIBE, true);
92
93 } else if (state == PJSIP_EVSUB_STATE_TERMINATED) {
94 int resub_delay = -1;
96
98 std::string(pres_client->getURI()),
99 PJ_FALSE);
100
101 pres_client->term_code_ = 200;
102
103 /* Determine whether to resubscribe automatically */
104 if (event && event->type == PJSIP_EVENT_TSX_STATE) {
105 const pjsip_transaction* tsx = event->body.tsx_state.tsx;
106
107 if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) {
108 pres_client->term_code_ = tsx->status_code;
109 std::ostringstream os;
110 os << pres_client->term_code_;
111 const std::string error = os.str() + "/" + sip_utils::as_view(pres_client->term_reason_);
112
113 std::string msg;
115
116 switch (tsx->status_code) {
118 /* 481: we refreshed too late? resubscribe
119 * immediately.
120 */
121 /* But this must only happen when the 481 is received
122 * on subscription refresh request. We MUST NOT try to
123 * resubscribe automatically if the 481 is received
124 * on the initial SUBSCRIBE (if server returns this
125 * response for some reason).
126 */
127 if (pres_client->dlg_->remote.contact)
128 resub_delay = 500;
129 msg = "Bad subscribe refresh.";
131 break;
132
134 msg = "Subscribe context not set for this buddy.";
136 break;
137
139 msg = "Subscribe not allowed for this buddy.";
141 break;
142
144 msg = "Wrong server.";
145 break;
146
147 default:
148 JAMI_WARNING("Unrecognized status code: {}", tsx->status_code);
149 }
150
151 /* report error:
152 * 1) send a signal through DBus
153 * 2) change the support field in the account schema if the pres_sub's server
154 * is the same as the account's server
155 */
156 emitSignal<libjami::PresenceSignal::ServerError>(pres_client->getPresence()->getAccount()->getAccountID(),
157 error,
158 msg);
159
162
164 pres_client->getPresence()->getAccount()->supportPresence(PRESENCE_FUNCTION_SUBSCRIBE, false);
165
166 } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
167 if (pres_client->isTermReason("deactivated") || pres_client->isTermReason("timeout")) {
168 /* deactivated: The subscription has been terminated,
169 * but the subscriber SHOULD retry immediately with
170 * a new subscription.
171 */
172 /* timeout: The subscription has been terminated
173 * because it was not refreshed before it expired.
174 * Clients MAY re-subscribe immediately. The
175 * "retry-after" parameter has no semantics for
176 * "timeout".
177 */
178 resub_delay = 500;
179 } else if (pres_client->isTermReason("probation") || pres_client->isTermReason("giveup")) {
180 /* probation: The subscription has been terminated,
181 * but the client SHOULD retry at some later time.
182 * If a "retry-after" parameter is also present, the
183 * client SHOULD wait at least the number of seconds
184 * specified by that parameter before attempting to re-
185 * subscribe.
186 */
187 /* giveup: The subscription has been terminated because
188 * the notifier was unable to obtain authorization in a
189 * timely fashion. If a "retry-after" parameter is
190 * also present, the client SHOULD wait at least the
191 * number of seconds specified by that parameter before
192 * attempting to re-subscribe; otherwise, the client
193 * MAY retry immediately, but will likely get put back
194 * into pending state.
195 */
197 constexpr pj_str_t sub_state = CONST_PJ_STR("Subscription-State");
198 const pjsip_msg* msg;
199
200 msg = event->body.tsx_state.src.rdata->msg_info.msg;
202
203 if (sub_hdr && sub_hdr->retry_after > 0)
204 resub_delay = sub_hdr->retry_after * 1000;
205 }
206 }
207 }
208
209 /* For other cases of subscription termination, if resubscribe
210 * timer is not set, schedule with default expiration (plus minus
211 * some random value, to avoid sending SUBSCRIBEs all at once)
212 */
213 if (resub_delay == -1) {
214 resub_delay = PRES_TIMER * 1000;
215 }
216
217 pres_client->sub_ = sub;
218 pres_client->rescheduleTimer(PJ_TRUE, resub_delay);
219
220 } else { // state==ACTIVE ......
221 // This will clear the last termination code/reason
222 pres_client->term_code_ = 0;
223 pres_client->term_reason_.ptr = NULL;
224 }
225
226 /* Clear subscription */
228 pjsip_evsub_terminate(pres_client->sub_, PJ_FALSE); // = NULL;
229 pres_client->status_.info_cnt = 0;
230 pres_client->dlg_ = NULL;
231 pres_client->rescheduleTimer(PJ_FALSE, 0);
233
234 pres_client->enable(false);
235 }
236}
237
238/* Callback when transaction state has changed. */
239void
240PresSubClient::pres_client_evsub_on_tsx_state(pjsip_evsub* sub, pjsip_transaction* tsx, pjsip_event* event)
241{
242 PresSubClient* pres_client;
244
245 pres_client = (PresSubClient*) pjsip_evsub_get_mod_data(sub, modId_);
246 /* No need to pres->lock() here since the client has a locked dialog*/
247
248 if (!pres_client) {
249 JAMI_WARN("Unable to find pres_client.");
250 return;
251 }
252
253 /* We only use this to update pres_client's Contact, when it's not
254 * set.
255 */
256 if (pres_client->contact_.slen != 0) {
257 /* Contact already set */
258 return;
259 }
260
261 /* Only care about 2xx response to outgoing SUBSCRIBE */
262 if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE || event->type != PJSIP_EVENT_RX_MSG
263 || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) {
264 return;
265 }
266
267 /* Find contact header. */
268 contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
269
270 if (!contact_hdr || !contact_hdr->uri) {
271 return;
272 }
273
274 pres_client->contact_.ptr = (char*) pj_pool_alloc(pres_client->pool_, PJSIP_MAX_URL_SIZE);
276 contact_hdr->uri,
277 pres_client->contact_.ptr,
279
280 if (pres_client->contact_.slen < 0)
281 pres_client->contact_.slen = 0;
282}
283
284/* Callback called when we receive NOTIFY */
285void
286PresSubClient::pres_client_evsub_on_rx_notify(pjsip_evsub* sub,
288 int* p_st_code,
292{
293 PresSubClient* pres_client = (PresSubClient*) pjsip_evsub_get_mod_data(sub, modId_);
294
295 if (!pres_client) {
296 JAMI_WARN("Unable to find pres_client from ev_sub.");
297 return;
298 }
299 /* No need to pres->lock() here since the client has a locked dialog*/
300
302 pres_client->reportPresence();
303
304 /* The default is to send 200 response to NOTIFY.
305 * Just leave it there.
306 */
312}
313
315 : pres_(pres)
316 , uri_ {0, 0}
317 , contact_ {0, 0}
318 , display_()
319 , dlg_(NULL)
320 , monitored_(false)
321 , name_()
322 , cp_()
323 , pool_(0)
324 , status_()
325 , sub_(NULL)
326 , term_code_(0)
327 , term_reason_()
328 , timer_()
329 , user_data_(NULL)
330 , lock_count_(0)
331 , lock_flag_(0)
332{
334 pool_ = pj_pool_create(&cp_.factory, "Pres_sub_client", 512, 512, NULL);
335 uri_ = pj_strdup3(pool_, uri.c_str());
336 contact_ = pj_strdup3(pool_, pres_->getAccount()->getFromUri().c_str());
337}
338
340{
341 JAMI_DBG("Destroying pres_client object with uri %.*s", (int) uri_.slen, uri_.ptr);
342 rescheduleTimer(PJ_FALSE, 0);
343 unsubscribe();
344 pj_pool_release(pool_);
345}
346
347bool
349{
350 return monitored_;
351}
352
353std::string_view
355{
356 return {uri_.ptr, (size_t) uri_.slen};
357}
358
361{
362 return pres_;
363}
364
365bool
367{
368 return status_.info[0].basic_open;
369}
370
371std::string_view
373{
374 return {status_.info[0].rpid.note.ptr, (size_t) status_.info[0].rpid.note.slen};
375}
376
377bool
378PresSubClient::isTermReason(const std::string& reason)
379{
380 const std::string_view myReason(term_reason_.ptr, (size_t) term_reason_.slen);
381 return not myReason.compare(reason);
382}
383
384void
385PresSubClient::rescheduleTimer(bool reschedule, unsigned msec)
386{
387 if (timer_.id) {
388 pjsip_endpt_cancel_timer(Manager::instance().sipVoIPLink().getEndpoint(), &timer_);
389 timer_.id = PJ_FALSE;
390 }
391
392 if (reschedule) {
394
395 JAMI_WARN("pres_client %.*s will resubscribe in %u ms (reason: %.*s)",
396 (int) uri_.slen,
397 uri_.ptr,
398 msec,
399 (int) term_reason_.slen,
400 term_reason_.ptr);
401 pj_timer_entry_init(&timer_, 0, this, &pres_client_timer_cb);
402 delay.sec = 0;
403 delay.msec = msec;
405
406 if (pjsip_endpt_schedule_timer(Manager::instance().sipVoIPLink().getEndpoint(), &timer_, &delay) == PJ_SUCCESS) {
407 timer_.id = PJ_TRUE;
408 }
409 }
410}
411
412void
414{
415 JAMI_DBG("pres_client %.*s is %s monitored.", (int) getURI().size(), getURI().data(), flag ? "" : "NOT");
416 if (flag and not monitored_)
417 pres_->addPresSubClient(this);
418 monitored_ = flag;
419}
420
421void
422PresSubClient::reportPresence()
423{
424 /* callback*/
425 pres_->reportPresSubClientNotification(getURI(), &status_);
426}
427
428bool
430{
431 unsigned i;
432
433 for (i = 0; i < 50; i++) {
434 if (not pres_->tryLock()) {
435 // FIXME: i/10 in ms, sure!?
436 std::this_thread::sleep_for(std::chrono::milliseconds(i / 10));
437 continue;
438 }
439 lock_flag_ = PRESENCE_LOCK_FLAG;
440
441 if (dlg_ == NULL) {
442 pres_->unlock();
443 return true;
444 }
445
446 if (pjsip_dlg_try_inc_lock(dlg_) != PJ_SUCCESS) {
447 lock_flag_ = 0;
448 pres_->unlock();
449 // FIXME: i/10 in ms, sure!?
450 std::this_thread::sleep_for(std::chrono::milliseconds(i / 10));
451 continue;
452 }
453
454 lock_flag_ = PRESENCE_CLIENT_LOCK_FLAG;
455 pres_->unlock();
456 }
457
458 if (lock_flag_ == 0) {
459 JAMI_DBG("pres_client failed to lock : timeout");
460 return false;
461 }
462 return true;
463}
464
465void
467{
468 if (lock_flag_ & PRESENCE_CLIENT_LOCK_FLAG)
469 pjsip_dlg_dec_lock(dlg_);
470
471 if (lock_flag_ & PRESENCE_LOCK_FLAG)
472 pres_->unlock();
473}
474
475bool
477{
478 if (not lock())
479 return false;
480
481 monitored_ = false;
482
485
486 if (sub_ == NULL or dlg_ == NULL) {
487 JAMI_WARN("PresSubClient already unsubscribed.");
488 unlock();
489 return false;
490 }
491
493 JAMI_WARN("pres_client already unsubscribed sub=TERMINATED.");
494 sub_ = NULL;
495 unlock();
496 return false;
497 }
498
499 /* Unsubscribe means send a subscribe with timeout=0s*/
500 JAMI_WARN("pres_client %.*s: unsubscribing..", (int) uri_.slen, uri_.ptr);
502
503 if (retStatus == PJ_SUCCESS) {
504 pres_->fillDoc(tdata, NULL);
506 }
507
508 if (retStatus != PJ_SUCCESS and sub_) {
510 sub_ = NULL;
511 JAMI_WARN("Unable to unsubscribe presence (%d)", retStatus);
512 unlock();
513 return false;
514 }
515
516 // pjsip_evsub_set_mod_data(sub_, modId_, NULL); // Not interested with further events
517
518 unlock();
519 return true;
520}
521
522bool
524{
525 if (sub_ and dlg_) { // do not bother if already subscribed
527 JAMI_DBG("PreseSubClient %.*s: already subscribed. Refresh it.", (int) uri_.slen, uri_.ptr);
528 }
529
530 // subscribe
533 pj_status_t status;
534
535 /* Event subscription callback. */
537 pres_callback.on_evsub_state = &pres_client_evsub_on_state;
538 pres_callback.on_tsx_state = &pres_client_evsub_on_tsx_state;
539 pres_callback.on_rx_notify = &pres_client_evsub_on_rx_notify;
540
541 SIPAccount* acc = pres_->getAccount();
542 JAMI_DBG("PresSubClient %.*s: subscribing ", (int) uri_.slen, uri_.ptr);
543
544 /* Create UAC dialog */
545 pj_str_t from = pj_strdup3(pool_, acc->getFromUri().c_str());
546 status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact_, &uri_, NULL, &dlg_);
547
548 if (status != PJ_SUCCESS) {
549 JAMI_ERR("Unable to create dialog \n");
550 return false;
551 }
552
553 /* Add credential for auth. */
554 if (acc->hasCredentials()
555 and pjsip_auth_clt_set_credentials(&dlg_->auth_sess,
556 static_cast<int>(acc->getCredentialCount()),
557 acc->getCredInfo())
558 != PJ_SUCCESS) {
559 JAMI_ERR("Unable to initialize credentials for subscribe session authentication");
560 }
561
562 /* Increment the dialog's lock otherwise when presence session creation
563 * fails the dialog will be destroyed prematurely.
564 */
565 pjsip_dlg_inc_lock(dlg_);
566
568
569 if (status != PJ_SUCCESS) {
570 sub_ = NULL;
571 JAMI_WARN("Unable to create presence client (%d)", status);
572
573 /* This should destroy the dialog since there's no session
574 * referencing it
575 */
576 if (dlg_) {
577 pjsip_dlg_dec_lock(dlg_);
578 }
579
580 return false;
581 }
582
583 /* Add credential for authentication */
584 if (acc->hasCredentials()
585 and pjsip_auth_clt_set_credentials(&dlg_->auth_sess,
586 static_cast<int>(acc->getCredentialCount()),
587 acc->getCredInfo())
588 != PJ_SUCCESS) {
589 JAMI_ERR("Unable to initialize credentials for invite session authentication");
590 return false;
591 }
592
593 /* Set route-set */
595 if (regc and acc->hasServiceRoute())
597
598 // attach the client data to the sub
599 pjsip_evsub_set_mod_data(sub_, modId_, this);
600
601 status = pjsip_pres_initiate(sub_, -1, &tdata);
602 if (status != PJ_SUCCESS) {
603 if (dlg_)
604 pjsip_dlg_dec_lock(dlg_);
605 if (sub_)
607 sub_ = NULL;
608 JAMI_WARN("Unable to create initial SUBSCRIBE (%d)", status);
609 return false;
610 }
611
612 // pjsua_process_msg_data(tdata, NULL);
613
614 status = pjsip_pres_send_request(sub_, tdata);
615
616 if (status != PJ_SUCCESS) {
617 if (dlg_)
618 pjsip_dlg_dec_lock(dlg_);
619 if (sub_)
621 sub_ = NULL;
622 JAMI_WARN("Unable to send initial SUBSCRIBE (%d)", status);
623 return false;
624 }
625
626 pjsip_dlg_dec_lock(dlg_);
627 return true;
628}
629
630bool
632{
633 return (b->getURI() == getURI());
634}
635
636} // namespace jami
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
void enable(bool flag)
Enable the monitoring and report signal to the client.
bool isPresent()
Is the buddy present.
PresSubClient(const std::string &uri, SIPPresence *pres)
Constructor.
bool unsubscribe()
Send a SUBCRIBE to the PXB or directly to a pres_client in the IP2IP context but the 0s timeout make ...
bool match(PresSubClient *b)
Compare with another pres_client's uris.
bool isSubscribed()
Return the monitor variable.
bool lock()
Data lock function.
bool subscribe()
Send a SUBCRIBE to the PXB or directly to a pres_client in the IP2IP context.
std::string_view getURI()
Return the pres_client URI.
void unlock()
Data unlock function.
std::string_view getLineStatus()
A message from the URIs.
SIPPresence * getPresence()
Get associated parent presence_module.
const pjsip_cred_info * getCredInfo() const
Definition sipaccount.h:148
unsigned getCredentialCount() const
Get the number of credentials defined for this account.
Definition sipaccount.h:156
bool hasServiceRoute() const
Definition sipaccount.h:301
bool hasCredentials() const
Definition sipaccount.h:158
std::string getFromUri() const override
std::string getServiceRoute() const
Definition sipaccount.h:299
pjsip_regc * getRegistrationInfo()
Get the registration structure that is used for PJSIP in the registration process.
Definition sipaccount.h:190
void fillDoc(pjsip_tx_data *tdata, const pres_msg_data *msg_data)
Fill xml document, the header and the body.
void reportPresSubClientNotification(std::string_view uri, pjsip_pres_status *status)
Send a signal to the client on DBus.
SIPAccount * getAccount() const
Return associated sipaccount.
void addPresSubClient(PresSubClient *b)
Add a buddy in the buddy list.
pj_pool_t * getPool() const
Return a pool for generic functions.
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
std::string_view getHostFromUri(std::string_view uri)
pjsip_route_hdr * createRouteSet(const std::string &route, pj_pool_t *hdr_pool)
Definition sip_utils.cpp:77
constexpr std::string_view as_view(const pj_str_t &str) noexcept
Definition sip_utils.h:105
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
Definition sip_utils.h:87
void emitSignal(Args... args)
Definition jami_signal.h:64
#define PRES_TIMER
A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink)
A SIP Presence manages buddy subscription in both PBX and IP2IP contexts.
#define PRESENCE_FUNCTION_SUBSCRIBE
Definition sippresence.h:34
#define PRESENCE_LOCK_FLAG
Definition sippresence.h:35
#define PRESENCE_CLIENT_LOCK_FLAG
Definition sippresence.h:36