Ring Daemon 16.0.0
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/ring_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(), pres_client->getURI().data(),
79
81
82 SIPPresence* pres = pres_client->getPresence();
83
84 if (state == PJSIP_EVSUB_STATE_ACCEPTED) {
85 pres_client->enable(true);
87 ->getAccountID(),
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 &pres_client->term_reason_,
98
100 ->getAccountID(),
101 std::string(pres_client->getURI()),
102 PJ_FALSE);
103
104 pres_client->term_code_ = 200;
105
106 /* Determine whether to resubscribe automatically */
107 if (event && event->type == PJSIP_EVENT_TSX_STATE) {
108 const pjsip_transaction* tsx = event->body.tsx_state.tsx;
109
110 if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) {
111 pres_client->term_code_ = tsx->status_code;
112 std::ostringstream os;
113 os << pres_client->term_code_;
114 const std::string error = os.str() + "/"
115 + sip_utils::as_view(pres_client->term_reason_);
116
117 std::string msg;
119
120 switch (tsx->status_code) {
122 /* 481: we refreshed too late? resubscribe
123 * immediately.
124 */
125 /* But this must only happen when the 481 is received
126 * on subscription refresh request. We MUST NOT try to
127 * resubscribe automatically if the 481 is received
128 * on the initial SUBSCRIBE (if server returns this
129 * response for some reason).
130 */
131 if (pres_client->dlg_->remote.contact)
132 resub_delay = 500;
133 msg = "Bad subscribe refresh.";
135 break;
136
138 msg = "Subscribe context not set for this buddy.";
140 break;
141
143 msg = "Subscribe not allowed for this buddy.";
145 break;
146
148 msg = "Wrong server.";
149 break;
150 }
151
152 /* report error:
153 * 1) send a signal through DBus
154 * 2) change the support field in the account schema if the pres_sub's server
155 * is the same as the account's server
156 */
158 pres_client->getPresence()->getAccount()->getAccountID(), error, msg);
159
162
164 pres_client->getPresence()
165 ->getAccount()
166 ->supportPresence(PRESENCE_FUNCTION_SUBSCRIBE, false);
167
168 } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
169 if (pres_client->isTermReason("deactivated")
170 || pres_client->isTermReason("timeout")) {
171 /* deactivated: The subscription has been terminated,
172 * but the subscriber SHOULD retry immediately with
173 * a new subscription.
174 */
175 /* timeout: The subscription has been terminated
176 * because it was not refreshed before it expired.
177 * Clients MAY re-subscribe immediately. The
178 * "retry-after" parameter has no semantics for
179 * "timeout".
180 */
181 resub_delay = 500;
182 } else if (pres_client->isTermReason("probation")
183 || pres_client->isTermReason("giveup")) {
184 /* probation: The subscription has been terminated,
185 * but the client SHOULD retry at some later time.
186 * If a "retry-after" parameter is also present, the
187 * client SHOULD wait at least the number of seconds
188 * specified by that parameter before attempting to re-
189 * subscribe.
190 */
191 /* giveup: The subscription has been terminated because
192 * the notifier was unable to obtain authorization in a
193 * timely fashion. If a "retry-after" parameter is
194 * also present, the client SHOULD wait at least the
195 * number of seconds specified by that parameter before
196 * attempting to re-subscribe; otherwise, the client
197 * MAY retry immediately, but will likely get put back
198 * into pending state.
199 */
201 constexpr pj_str_t sub_state = CONST_PJ_STR("Subscription-State");
202 const pjsip_msg* msg;
203
204 msg = event->body.tsx_state.src.rdata->msg_info.msg;
206 &sub_state,
207 NULL);
208
209 if (sub_hdr && sub_hdr->retry_after > 0)
210 resub_delay = sub_hdr->retry_after * 1000;
211 }
212 }
213 }
214
215 /* For other cases of subscription termination, if resubscribe
216 * timer is not set, schedule with default expiration (plus minus
217 * some random value, to avoid sending SUBSCRIBEs all at once)
218 */
219 if (resub_delay == -1) {
220 resub_delay = PRES_TIMER * 1000;
221 }
222
223 pres_client->sub_ = sub;
224 pres_client->rescheduleTimer(PJ_TRUE, resub_delay);
225
226 } else { // state==ACTIVE ......
227 // This will clear the last termination code/reason
228 pres_client->term_code_ = 0;
229 pres_client->term_reason_.ptr = NULL;
230 }
231
232 /* Clear subscription */
234 pjsip_evsub_terminate(pres_client->sub_, PJ_FALSE); // = NULL;
235 pres_client->status_.info_cnt = 0;
236 pres_client->dlg_ = NULL;
237 pres_client->rescheduleTimer(PJ_FALSE, 0);
239
240 pres_client->enable(false);
241 }
242}
243
244/* Callback when transaction state has changed. */
245void
246PresSubClient::pres_client_evsub_on_tsx_state(pjsip_evsub* sub,
249{
250 PresSubClient* pres_client;
252
253 pres_client = (PresSubClient*) pjsip_evsub_get_mod_data(sub, modId_);
254 /* No need to pres->lock() here since the client has a locked dialog*/
255
256 if (!pres_client) {
257 JAMI_WARN("Unable to find pres_client.");
258 return;
259 }
260
261 /* We only use this to update pres_client's Contact, when it's not
262 * set.
263 */
264 if (pres_client->contact_.slen != 0) {
265 /* Contact already set */
266 return;
267 }
268
269 /* Only care about 2xx response to outgoing SUBSCRIBE */
270 if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE
271 || event->type != PJSIP_EVENT_RX_MSG
272 || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) {
273 return;
274 }
275
276 /* Find contact header. */
277 contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
279 NULL);
280
281 if (!contact_hdr || !contact_hdr->uri) {
282 return;
283 }
284
285 pres_client->contact_.ptr = (char*) pj_pool_alloc(pres_client->pool_, PJSIP_MAX_URL_SIZE);
287 contact_hdr->uri,
288 pres_client->contact_.ptr,
290
291 if (pres_client->contact_.slen < 0)
292 pres_client->contact_.slen = 0;
293}
294
295/* Callback called when we receive NOTIFY */
296void
297PresSubClient::pres_client_evsub_on_rx_notify(pjsip_evsub* sub,
299 int* p_st_code,
303{
304 PresSubClient* pres_client = (PresSubClient*) pjsip_evsub_get_mod_data(sub, modId_);
305
306 if (!pres_client) {
307 JAMI_WARN("Unable to find pres_client from ev_sub.");
308 return;
309 }
310 /* No need to pres->lock() here since the client has a locked dialog*/
311
313 pres_client->reportPresence();
314
315 /* The default is to send 200 response to NOTIFY.
316 * Just leave it there.
317 */
323}
324
326 : pres_(pres)
327 , uri_ {0, 0}
328 , contact_ {0, 0}
329 , display_()
330 , dlg_(NULL)
331 , monitored_(false)
332 , name_()
333 , cp_()
334 , pool_(0)
335 , status_()
336 , sub_(NULL)
337 , term_code_(0)
338 , term_reason_()
339 , timer_()
340 , user_data_(NULL)
341 , lock_count_(0)
342 , lock_flag_(0)
343{
345 pool_ = pj_pool_create(&cp_.factory, "Pres_sub_client", 512, 512, NULL);
346 uri_ = pj_strdup3(pool_, uri.c_str());
347 contact_ = pj_strdup3(pool_, pres_->getAccount()->getFromUri().c_str());
348}
349
351{
352 JAMI_DBG("Destroying pres_client object with uri %.*s", (int) uri_.slen, uri_.ptr);
353 rescheduleTimer(PJ_FALSE, 0);
354 unsubscribe();
355 pj_pool_release(pool_);
356}
357
358bool
360{
361 return monitored_;
362}
363
364std::string_view
366{
367 return {uri_.ptr, (size_t)uri_.slen};
368}
369
372{
373 return pres_;
374}
375
376bool
378{
379 return status_.info[0].basic_open;
380}
381
382std::string_view
384{
385 return {status_.info[0].rpid.note.ptr, (size_t)status_.info[0].rpid.note.slen};
386}
387
388bool
389PresSubClient::isTermReason(const std::string& reason)
390{
391 const std::string_view myReason(term_reason_.ptr, (size_t)term_reason_.slen);
392 return not myReason.compare(reason);
393}
394
395void
396PresSubClient::rescheduleTimer(bool reschedule, unsigned msec)
397{
398 if (timer_.id) {
399 pjsip_endpt_cancel_timer(Manager::instance().sipVoIPLink().getEndpoint(), &timer_);
400 timer_.id = PJ_FALSE;
401 }
402
403 if (reschedule) {
405
406 JAMI_WARN("pres_client %.*s will resubscribe in %u ms (reason: %.*s)",
407 (int) uri_.slen,
408 uri_.ptr,
409 msec,
410 (int) term_reason_.slen,
411 term_reason_.ptr);
412 pj_timer_entry_init(&timer_, 0, this, &pres_client_timer_cb);
413 delay.sec = 0;
414 delay.msec = msec;
416
417 if (pjsip_endpt_schedule_timer(Manager::instance().sipVoIPLink().getEndpoint(),
418 &timer_,
419 &delay)
420 == PJ_SUCCESS) {
421 timer_.id = PJ_TRUE;
422 }
423 }
424}
425
426void
428{
429 JAMI_DBG("pres_client %.*s is %s monitored.", (int)getURI().size(), getURI().data(), flag ? "" : "NOT");
430 if (flag and not monitored_)
431 pres_->addPresSubClient(this);
432 monitored_ = flag;
433}
434
435void
436PresSubClient::reportPresence()
437{
438 /* callback*/
439 pres_->reportPresSubClientNotification(getURI(), &status_);
440}
441
442bool
444{
445 unsigned i;
446
447 for (i = 0; i < 50; i++) {
448 if (not pres_->tryLock()) {
449 // FIXME: i/10 in ms, sure!?
450 std::this_thread::sleep_for(std::chrono::milliseconds(i / 10));
451 continue;
452 }
453 lock_flag_ = PRESENCE_LOCK_FLAG;
454
455 if (dlg_ == NULL) {
456 pres_->unlock();
457 return true;
458 }
459
460 if (pjsip_dlg_try_inc_lock(dlg_) != PJ_SUCCESS) {
461 lock_flag_ = 0;
462 pres_->unlock();
463 // FIXME: i/10 in ms, sure!?
464 std::this_thread::sleep_for(std::chrono::milliseconds(i / 10));
465 continue;
466 }
467
468 lock_flag_ = PRESENCE_CLIENT_LOCK_FLAG;
469 pres_->unlock();
470 }
471
472 if (lock_flag_ == 0) {
473 JAMI_DBG("pres_client failed to lock : timeout");
474 return false;
475 }
476 return true;
477}
478
479void
481{
482 if (lock_flag_ & PRESENCE_CLIENT_LOCK_FLAG)
483 pjsip_dlg_dec_lock(dlg_);
484
485 if (lock_flag_ & PRESENCE_LOCK_FLAG)
486 pres_->unlock();
487}
488
489bool
491{
492 if (not lock())
493 return false;
494
495 monitored_ = false;
496
499
500 if (sub_ == NULL or dlg_ == NULL) {
501 JAMI_WARN("PresSubClient already unsubscribed.");
502 unlock();
503 return false;
504 }
505
507 JAMI_WARN("pres_client already unsubscribed sub=TERMINATED.");
508 sub_ = NULL;
509 unlock();
510 return false;
511 }
512
513 /* Unsubscribe means send a subscribe with timeout=0s*/
514 JAMI_WARN("pres_client %.*s: unsubscribing..", (int) uri_.slen, uri_.ptr);
516
517 if (retStatus == PJ_SUCCESS) {
518 pres_->fillDoc(tdata, NULL);
520 }
521
522 if (retStatus != PJ_SUCCESS and sub_) {
524 sub_ = NULL;
525 JAMI_WARN("Unable to unsubscribe presence (%d)", retStatus);
526 unlock();
527 return false;
528 }
529
530 // pjsip_evsub_set_mod_data(sub_, modId_, NULL); // Not interested with further events
531
532 unlock();
533 return true;
534}
535
536bool
538{
539 if (sub_ and dlg_) { // do not bother if already subscribed
541 JAMI_DBG("PreseSubClient %.*s: already subscribed. Refresh it.", (int) uri_.slen, uri_.ptr);
542 }
543
544 // subscribe
547 pj_status_t status;
548
549 /* Event subscription callback. */
551 pres_callback.on_evsub_state = &pres_client_evsub_on_state;
552 pres_callback.on_tsx_state = &pres_client_evsub_on_tsx_state;
553 pres_callback.on_rx_notify = &pres_client_evsub_on_rx_notify;
554
555 SIPAccount* acc = pres_->getAccount();
556 JAMI_DBG("PresSubClient %.*s: subscribing ", (int) uri_.slen, uri_.ptr);
557
558 /* Create UAC dialog */
559 pj_str_t from = pj_strdup3(pool_, acc->getFromUri().c_str());
560 status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact_, &uri_, NULL, &dlg_);
561
562 if (status != PJ_SUCCESS) {
563 JAMI_ERR("Unable to create dialog \n");
564 return false;
565 }
566
567 /* Add credential for auth. */
568 if (acc->hasCredentials()
569 and pjsip_auth_clt_set_credentials(&dlg_->auth_sess,
570 acc->getCredentialCount(),
571 acc->getCredInfo())
572 != PJ_SUCCESS) {
573 JAMI_ERR("Unable to initialize credentials for subscribe session authentication");
574 }
575
576 /* Increment the dialog's lock otherwise when presence session creation
577 * fails the dialog will be destroyed prematurely.
578 */
579 pjsip_dlg_inc_lock(dlg_);
580
582
583 if (status != PJ_SUCCESS) {
584 sub_ = NULL;
585 JAMI_WARN("Unable to create presence client (%d)", status);
586
587 /* This should destroy the dialog since there's no session
588 * referencing it
589 */
590 if (dlg_) {
591 pjsip_dlg_dec_lock(dlg_);
592 }
593
594 return false;
595 }
596
597 /* Add credential for authentication */
598 if (acc->hasCredentials()
599 and pjsip_auth_clt_set_credentials(&dlg_->auth_sess,
600 acc->getCredentialCount(),
601 acc->getCredInfo())
602 != PJ_SUCCESS) {
603 JAMI_ERR("Unable to initialize credentials for invite session authentication");
604 return false;
605 }
606
607 /* Set route-set */
609 if (regc and acc->hasServiceRoute())
612 pres_->getPool()));
613
614 // attach the client data to the sub
615 pjsip_evsub_set_mod_data(sub_, modId_, this);
616
617 status = pjsip_pres_initiate(sub_, -1, &tdata);
618 if (status != PJ_SUCCESS) {
619 if (dlg_)
620 pjsip_dlg_dec_lock(dlg_);
621 if (sub_)
623 sub_ = NULL;
624 JAMI_WARN("Unable to create initial SUBSCRIBE (%d)", status);
625 return false;
626 }
627
628 // pjsua_process_msg_data(tdata, NULL);
629
630 status = pjsip_pres_send_request(sub_, tdata);
631
632 if (status != PJ_SUCCESS) {
633 if (dlg_)
634 pjsip_dlg_dec_lock(dlg_);
635 if (sub_)
637 sub_ = NULL;
638 JAMI_WARN("Unable to send initial SUBSCRIBE (%d)", status);
639 return false;
640 }
641
642 pjsip_dlg_dec_lock(dlg_);
643 return true;
644}
645
646bool
648{
649 return (b->getURI() == getURI());
650}
651
652} // namespace jami
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
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:156
unsigned getCredentialCount() const
Get the number of credentials defined for this account.
Definition sipaccount.h:164
bool hasServiceRoute() const
Definition sipaccount.h:312
bool hasCredentials() const
Definition sipaccount.h:166
std::string getFromUri() const override
std::string getServiceRoute() const
Definition sipaccount.h:310
pjsip_regc * getRegistrationInfo()
Get the registration structure that is used for PJSIP in the registration process.
Definition sipaccount.h:201
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:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
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: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
#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:35
#define PRESENCE_LOCK_FLAG
Definition sippresence.h:36
#define PRESENCE_CLIENT_LOCK_FLAG
Definition sippresence.h:37