blob: a9c31073d9e6849ac2268f7dcef71015bcaf144b [file] [log] [blame]
Alexandre Lision67916dd2014-01-24 13:33:04 -05001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_pres.c"
25
26
27static void subscribe_buddy_presence(pjsua_buddy_id buddy_id);
28static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id);
29
30
31/*
32 * Find buddy.
33 */
34static pjsua_buddy_id find_buddy(const pjsip_uri *uri)
35{
36 const pjsip_sip_uri *sip_uri;
37 unsigned i;
38
39 uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri);
40
41 if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
42 return PJSUA_INVALID_ID;
43
44 sip_uri = (const pjsip_sip_uri*) uri;
45
46 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
47 const pjsua_buddy *b = &pjsua_var.buddy[i];
48
49 if (!pjsua_buddy_is_valid(i))
50 continue;
51
52 if (pj_stricmp(&sip_uri->user, &b->name)==0 &&
53 pj_stricmp(&sip_uri->host, &b->host)==0 &&
54 (sip_uri->port==(int)b->port || (sip_uri->port==0 && b->port==5060)))
55 {
56 /* Match */
57 return i;
58 }
59 }
60
61 return PJSUA_INVALID_ID;
62}
63
64#define LOCK_DIALOG 1
65#define LOCK_PJSUA 2
66#define LOCK_ALL (LOCK_DIALOG | LOCK_PJSUA)
67
68/* Buddy lock object */
69struct buddy_lock
70{
71 pjsua_buddy *buddy;
72 pjsip_dialog *dlg;
73 pj_uint8_t flag;
74};
75
76/* Acquire lock to the specified buddy_id */
77pj_status_t lock_buddy(const char *title,
78 pjsua_buddy_id buddy_id,
79 struct buddy_lock *lck,
80 unsigned _unused_)
81{
82 enum { MAX_RETRY=50 };
83 pj_bool_t has_pjsua_lock = PJ_FALSE;
84 unsigned retry;
85
86 PJ_UNUSED_ARG(_unused_);
87
88 pj_bzero(lck, sizeof(*lck));
89
90 for (retry=0; retry<MAX_RETRY; ++retry) {
91
92 if (PJSUA_TRY_LOCK() != PJ_SUCCESS) {
93 pj_thread_sleep(retry/10);
94 continue;
95 }
96
97 has_pjsua_lock = PJ_TRUE;
98 lck->flag = LOCK_PJSUA;
99 lck->buddy = &pjsua_var.buddy[buddy_id];
100
101 if (lck->buddy->dlg == NULL)
102 return PJ_SUCCESS;
103
104 if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) {
105 lck->flag = 0;
106 lck->buddy = NULL;
107 has_pjsua_lock = PJ_FALSE;
108 PJSUA_UNLOCK();
109 pj_thread_sleep(retry/10);
110 continue;
111 }
112
113 lck->dlg = lck->buddy->dlg;
114 lck->flag = LOCK_DIALOG;
115 PJSUA_UNLOCK();
116
117 break;
118 }
119
120 if (lck->flag == 0) {
121 if (has_pjsua_lock == PJ_FALSE)
122 PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
123 "(possibly system has deadlocked) in %s",
124 title));
125 else
126 PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
127 "(possibly system has deadlocked) in %s",
128 title));
129 return PJ_ETIMEDOUT;
130 }
131
132 return PJ_SUCCESS;
133}
134
135/* Release buddy lock */
136static void unlock_buddy(struct buddy_lock *lck)
137{
138 if (lck->flag & LOCK_DIALOG)
139 pjsip_dlg_dec_lock(lck->dlg);
140
141 if (lck->flag & LOCK_PJSUA)
142 PJSUA_UNLOCK();
143}
144
145
146/*
147 * Get total number of buddies.
148 */
149PJ_DEF(unsigned) pjsua_get_buddy_count(void)
150{
151 return pjsua_var.buddy_cnt;
152}
153
154
155/*
156 * Find buddy.
157 */
158PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str)
159{
160 pj_str_t input;
161 pj_pool_t *pool;
162 pjsip_uri *uri;
163 pjsua_buddy_id buddy_id;
164
165 pool = pjsua_pool_create("buddyfind", 512, 512);
166 pj_strdup_with_null(pool, &input, uri_str);
167
168 uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0);
169 if (!uri)
170 buddy_id = PJSUA_INVALID_ID;
171 else {
172 PJSUA_LOCK();
173 buddy_id = find_buddy(uri);
174 PJSUA_UNLOCK();
175 }
176
177 pj_pool_release(pool);
178
179 return buddy_id;
180}
181
182
183/*
184 * Check if buddy ID is valid.
185 */
186PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)
187{
188 return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) &&
189 pjsua_var.buddy[buddy_id].uri.slen != 0;
190}
191
192
193/*
194 * Enum buddy IDs.
195 */
196PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[],
197 unsigned *count)
198{
199 unsigned i, c;
200
201 PJ_ASSERT_RETURN(ids && count, PJ_EINVAL);
202
203 PJSUA_LOCK();
204
205 for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
206 if (!pjsua_var.buddy[i].uri.slen)
207 continue;
208 ids[c] = i;
209 ++c;
210 }
211
212 *count = c;
213
214 PJSUA_UNLOCK();
215
216 return PJ_SUCCESS;
217}
218
219
220/*
221 * Get detailed buddy info.
222 */
223PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id,
224 pjsua_buddy_info *info)
225{
226 pj_size_t total=0;
227 struct buddy_lock lck;
228 pjsua_buddy *buddy;
229 pj_status_t status;
230
231 PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
232
233 pj_bzero(info, sizeof(pjsua_buddy_info));
234
235 status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0);
236 if (status != PJ_SUCCESS)
237 return status;
238
239 buddy = lck.buddy;
240 info->id = buddy->index;
241 if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
242 unlock_buddy(&lck);
243 return PJ_SUCCESS;
244 }
245
246 /* uri */
247 info->uri.ptr = info->buf_ + total;
248 pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total);
249 total += info->uri.slen;
250
251 /* contact */
252 info->contact.ptr = info->buf_ + total;
253 pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total);
254 total += info->contact.slen;
255
256 /* Presence status */
257 pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status));
258
259 /* status and status text */
260 if (buddy->sub == NULL || buddy->status.info_cnt==0) {
261 info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
262 info->status_text = pj_str("?");
263 } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) {
264 info->status = PJSUA_BUDDY_STATUS_ONLINE;
265
266 /* copy RPID information */
267 info->rpid = buddy->status.info[0].rpid;
268
269 if (info->rpid.note.slen)
270 info->status_text = info->rpid.note;
271 else
272 info->status_text = pj_str("Online");
273
274 } else {
275 info->status = PJSUA_BUDDY_STATUS_OFFLINE;
276 info->rpid = buddy->status.info[0].rpid;
277
278 if (info->rpid.note.slen)
279 info->status_text = info->rpid.note;
280 else
281 info->status_text = pj_str("Offline");
282 }
283
284 /* monitor pres */
285 info->monitor_pres = buddy->monitor;
286
287 /* subscription state and termination reason */
288 info->sub_term_code = buddy->term_code;
289 if (buddy->sub) {
290 info->sub_state = pjsip_evsub_get_state(buddy->sub);
291 info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub);
292 if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED &&
293 total < sizeof(info->buf_))
294 {
295 info->sub_term_reason.ptr = info->buf_ + total;
296 pj_strncpy(&info->sub_term_reason,
297 pjsip_evsub_get_termination_reason(buddy->sub),
298 sizeof(info->buf_) - total);
299 total += info->sub_term_reason.slen;
300 } else {
301 info->sub_term_reason = pj_str("");
302 }
303 } else if (total < sizeof(info->buf_)) {
304 info->sub_state_name = "NULL";
305 info->sub_term_reason.ptr = info->buf_ + total;
306 pj_strncpy(&info->sub_term_reason, &buddy->term_reason,
307 sizeof(info->buf_) - total);
308 total += info->sub_term_reason.slen;
309 } else {
310 info->sub_state_name = "NULL";
311 info->sub_term_reason = pj_str("");
312 }
313
314 unlock_buddy(&lck);
315 return PJ_SUCCESS;
316}
317
318/*
319 * Set the user data associated with the buddy object.
320 */
321PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id,
322 void *user_data)
323{
324 struct buddy_lock lck;
325 pj_status_t status;
326
327 PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
328
329 status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0);
330 if (status != PJ_SUCCESS)
331 return status;
332
333 pjsua_var.buddy[buddy_id].user_data = user_data;
334
335 unlock_buddy(&lck);
336
337 return PJ_SUCCESS;
338}
339
340
341/*
342 * Get the user data associated with the budy object.
343 */
344PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)
345{
346 struct buddy_lock lck;
347 pj_status_t status;
348 void *user_data;
349
350 PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL);
351
352 status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0);
353 if (status != PJ_SUCCESS)
354 return NULL;
355
356 user_data = pjsua_var.buddy[buddy_id].user_data;
357
358 unlock_buddy(&lck);
359
360 return user_data;
361}
362
363
364/*
365 * Reset buddy descriptor.
366 */
367static void reset_buddy(pjsua_buddy_id id)
368{
369 pj_pool_t *pool = pjsua_var.buddy[id].pool;
370 pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id]));
371 pjsua_var.buddy[id].pool = pool;
372 pjsua_var.buddy[id].index = id;
373}
374
375
376/*
377 * Add new buddy.
378 */
379PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg,
380 pjsua_buddy_id *p_buddy_id)
381{
382 pjsip_name_addr *url;
383 pjsua_buddy *buddy;
384 pjsip_sip_uri *sip_uri;
385 int index;
386 pj_str_t tmp;
387
388 PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <=
389 PJ_ARRAY_SIZE(pjsua_var.buddy),
390 PJ_ETOOMANY);
391
392 PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s",
393 (int)cfg->uri.slen, cfg->uri.ptr));
394 pj_log_push_indent();
395
396 PJSUA_LOCK();
397
398 /* Find empty slot */
399 for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) {
400 if (pjsua_var.buddy[index].uri.slen == 0)
401 break;
402 }
403
404 /* Expect to find an empty slot */
405 if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) {
406 PJSUA_UNLOCK();
407 /* This shouldn't happen */
408 pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)");
409 pj_log_pop_indent();
410 return PJ_ETOOMANY;
411 }
412
413 buddy = &pjsua_var.buddy[index];
414
415 /* Create pool for this buddy */
416 if (buddy->pool) {
417 pj_pool_reset(buddy->pool);
418 } else {
419 char name[PJ_MAX_OBJ_NAME];
420 pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index);
421 buddy->pool = pjsua_pool_create(name, 512, 256);
422 }
423
424 /* Init buffers for presence subscription status */
425 buddy->term_reason.ptr = (char*)
426 pj_pool_alloc(buddy->pool,
427 PJSUA_BUDDY_SUB_TERM_REASON_LEN);
428
429 /* Get name and display name for buddy */
430 pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri);
431 url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen,
432 PJSIP_PARSE_URI_AS_NAMEADDR);
433
434 if (url == NULL) {
435 pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI);
436 pj_pool_release(buddy->pool);
437 buddy->pool = NULL;
438 PJSUA_UNLOCK();
439 pj_log_pop_indent();
440 return PJSIP_EINVALIDURI;
441 }
442
443 /* Only support SIP schemes */
444 if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) {
445 pj_pool_release(buddy->pool);
446 buddy->pool = NULL;
447 PJSUA_UNLOCK();
448 pj_log_pop_indent();
449 return PJSIP_EINVALIDSCHEME;
450 }
451
452 /* Reset buddy, to make sure everything is cleared with default
453 * values
454 */
455 reset_buddy(index);
456
457 /* Save URI */
458 pjsua_var.buddy[index].uri = tmp;
459
460 sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri);
461 pjsua_var.buddy[index].name = sip_uri->user;
462 pjsua_var.buddy[index].display = url->display;
463 pjsua_var.buddy[index].host = sip_uri->host;
464 pjsua_var.buddy[index].port = sip_uri->port;
465 pjsua_var.buddy[index].monitor = cfg->subscribe;
466 if (pjsua_var.buddy[index].port == 0)
467 pjsua_var.buddy[index].port = 5060;
468
469 /* Save user data */
470 pjsua_var.buddy[index].user_data = (void*)cfg->user_data;
471
472 if (p_buddy_id)
473 *p_buddy_id = index;
474
475 pjsua_var.buddy_cnt++;
476
477 PJSUA_UNLOCK();
478
479 PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index));
480
481 pjsua_buddy_subscribe_pres(index, cfg->subscribe);
482
483 pj_log_pop_indent();
484 return PJ_SUCCESS;
485}
486
487
488/*
489 * Delete buddy.
490 */
491PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
492{
493 struct buddy_lock lck;
494 pj_status_t status;
495
496 PJ_ASSERT_RETURN(buddy_id>=0 &&
497 buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
498 PJ_EINVAL);
499
500 if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
501 return PJ_SUCCESS;
502 }
503
504 status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0);
505 if (status != PJ_SUCCESS)
506 return status;
507
508 PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id));
509 pj_log_push_indent();
510
511 /* Unsubscribe presence */
512 pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
513
514 /* Not interested with further events for this buddy */
515 if (pjsua_var.buddy[buddy_id].sub) {
516 pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub,
517 pjsua_var.mod.id, NULL);
518 }
519
520 /* Remove buddy */
521 pjsua_var.buddy[buddy_id].uri.slen = 0;
522 pjsua_var.buddy_cnt--;
523
524 /* Clear timer */
525 if (pjsua_var.buddy[buddy_id].timer.id) {
526 pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer);
527 pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE;
528 }
529
530 /* Reset buddy struct */
531 reset_buddy(buddy_id);
532
533 unlock_buddy(&lck);
534 pj_log_pop_indent();
535 return PJ_SUCCESS;
536}
537
538
539/*
540 * Enable/disable buddy's presence monitoring.
541 */
542PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
543 pj_bool_t subscribe)
544{
545 struct buddy_lock lck;
546 pj_status_t status;
547
548 PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
549
550 status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0);
551 if (status != PJ_SUCCESS)
552 return status;
553
554 PJ_LOG(4,(THIS_FILE, "Buddy %d: unsubscribing presence..", buddy_id));
555 pj_log_push_indent();
556
557 lck.buddy->monitor = subscribe;
558
559 pjsua_buddy_update_pres(buddy_id);
560
561 unlock_buddy(&lck);
562 pj_log_pop_indent();
563 return PJ_SUCCESS;
564}
565
566
567/*
568 * Update buddy's presence.
569 */
570PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)
571{
572 struct buddy_lock lck;
573 pj_status_t status;
574
575 PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
576
577 status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0);
578 if (status != PJ_SUCCESS)
579 return status;
580
581 PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id));
582 pj_log_push_indent();
583
584 /* Is this an unsubscribe request? */
585 if (!lck.buddy->monitor) {
586 unsubscribe_buddy_presence(buddy_id);
587 unlock_buddy(&lck);
588 pj_log_pop_indent();
589 return PJ_SUCCESS;
590 }
591
592 /* Ignore if presence is already active for the buddy */
593 if (lck.buddy->sub) {
594 unlock_buddy(&lck);
595 pj_log_pop_indent();
596 return PJ_SUCCESS;
597 }
598
599 /* Initiate presence subscription */
600 subscribe_buddy_presence(buddy_id);
601
602 unlock_buddy(&lck);
603 pj_log_pop_indent();
604 return PJ_SUCCESS;
605}
606
607
608/*
609 * Dump presence subscriptions to log file.
610 */
611PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose)
612{
613 unsigned acc_id;
614 unsigned i;
615
616
617 PJSUA_LOCK();
618
619 /*
620 * When no detail is required, just dump number of server and client
621 * subscriptions.
622 */
623 if (verbose == PJ_FALSE) {
624
625 int count = 0;
626
627 for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
628
629 if (!pjsua_var.acc[acc_id].valid)
630 continue;
631
632 if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
633 struct pjsua_srv_pres *uapres;
634
635 uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
636 while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
637 ++count;
638 uapres = uapres->next;
639 }
640 }
641 }
642
643 PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d",
644 count));
645
646 count = 0;
647
648 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
649 if (pjsua_var.buddy[i].uri.slen == 0)
650 continue;
651 if (pjsua_var.buddy[i].sub) {
652 ++count;
653 }
654 }
655
656 PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d",
657 count));
658 PJSUA_UNLOCK();
659 return;
660 }
661
662
663 /*
664 * Dumping all server (UAS) subscriptions
665 */
666 PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
667
668 for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
669
670 if (!pjsua_var.acc[acc_id].valid)
671 continue;
672
673 PJ_LOG(3,(THIS_FILE, " %.*s",
674 (int)pjsua_var.acc[acc_id].cfg.id.slen,
675 pjsua_var.acc[acc_id].cfg.id.ptr));
676
677 if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
678
679 PJ_LOG(3,(THIS_FILE, " - none - "));
680
681 } else {
682 struct pjsua_srv_pres *uapres;
683
684 uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
685 while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
686
687 PJ_LOG(3,(THIS_FILE, " %10s %s",
688 pjsip_evsub_get_state_name(uapres->sub),
689 uapres->remote));
690
691 uapres = uapres->next;
692 }
693 }
694 }
695
696 /*
697 * Dumping all client (UAC) subscriptions
698 */
699 PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
700
701 if (pjsua_var.buddy_cnt == 0) {
702
703 PJ_LOG(3,(THIS_FILE, " - no buddy list - "));
704
705 } else {
706 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
707
708 if (pjsua_var.buddy[i].uri.slen == 0)
709 continue;
710
711 if (pjsua_var.buddy[i].sub) {
712 PJ_LOG(3,(THIS_FILE, " %10s %.*s",
713 pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub),
714 (int)pjsua_var.buddy[i].uri.slen,
715 pjsua_var.buddy[i].uri.ptr));
716 } else {
717 PJ_LOG(3,(THIS_FILE, " %10s %.*s",
718 "(null)",
719 (int)pjsua_var.buddy[i].uri.slen,
720 pjsua_var.buddy[i].uri.ptr));
721 }
722 }
723 }
724
725 PJSUA_UNLOCK();
726}
727
728
729/***************************************************************************
730 * Server subscription.
731 */
732
733/* Proto */
734static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
735
736/* The module instance. */
737static pjsip_module mod_pjsua_pres =
738{
739 NULL, NULL, /* prev, next. */
740 { "mod-pjsua-pres", 14 }, /* Name. */
741 -1, /* Id */
742 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
743 NULL, /* load() */
744 NULL, /* start() */
745 NULL, /* stop() */
746 NULL, /* unload() */
747 &pres_on_rx_request, /* on_rx_request() */
748 NULL, /* on_rx_response() */
749 NULL, /* on_tx_request. */
750 NULL, /* on_tx_response() */
751 NULL, /* on_tsx_state() */
752
753};
754
755
756/* Callback called when *server* subscription state has changed. */
757static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
758{
759 pjsua_srv_pres *uapres;
760
761 PJ_UNUSED_ARG(event);
762
763 PJSUA_LOCK();
764
765 uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
766 if (uapres) {
767 pjsip_evsub_state state;
768
769 PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s",
770 uapres->remote, pjsip_evsub_get_state_name(sub)));
771 pj_log_push_indent();
772
773 state = pjsip_evsub_get_state(sub);
774
775 if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
776 pj_str_t from;
777
778 from = uapres->dlg->remote.info_str;
779 (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id,
780 uapres, &from,
781 state, event);
782 }
783
784 if (state == PJSIP_EVSUB_STATE_TERMINATED) {
785 pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
786 pj_list_erase(uapres);
787 }
788 pj_log_pop_indent();
789 }
790
791 PJSUA_UNLOCK();
792}
793
794/* This is called when request is received.
795 * We need to check for incoming SUBSCRIBE request.
796 */
797static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
798{
799 int acc_id;
800 pjsua_acc *acc;
801 pj_str_t contact;
802 pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
803 pjsua_srv_pres *uapres;
804 pjsip_evsub *sub;
805 pjsip_evsub_user pres_cb;
806 pjsip_dialog *dlg;
807 pjsip_status_code st_code;
808 pj_str_t reason;
809 pjsip_expires_hdr *expires_hdr;
810 pjsua_msg_data msg_data;
811 pj_status_t status;
812
813 if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0)
814 return PJ_FALSE;
815
816 /* Incoming SUBSCRIBE: */
817
818 /* Don't want to accept the request if shutdown is in progress */
819 if (pjsua_var.thread_quit_flag) {
820 pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
821 PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
822 NULL, NULL);
823 return PJ_TRUE;
824 }
825
826 PJSUA_LOCK();
827
828 /* Find which account for the incoming request. */
829 acc_id = pjsua_acc_find_for_incoming(rdata);
830 acc = &pjsua_var.acc[acc_id];
831
832 PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d",
833 acc_id));
834 pj_log_push_indent();
835
836 /* Create suitable Contact header */
837 if (acc->contact.slen) {
838 contact = acc->contact;
839 } else {
840 status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
841 acc_id, rdata);
842 if (status != PJ_SUCCESS) {
843 pjsua_perror(THIS_FILE, "Unable to generate Contact header",
844 status);
845 PJSUA_UNLOCK();
846 pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
847 NULL, NULL);
848 pj_log_pop_indent();
849 return PJ_TRUE;
850 }
851 }
852
853 /* Create UAS dialog: */
854 status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
855 &contact, &dlg);
856 if (status != PJ_SUCCESS) {
857 pjsua_perror(THIS_FILE,
858 "Unable to create UAS dialog for subscription",
859 status);
860 PJSUA_UNLOCK();
861 pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
862 NULL, NULL);
863 pj_log_pop_indent();
864 return PJ_TRUE;
865 }
866
867 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
868 pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);
869 } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
870 /* Choose local interface to use in Via if acc is not using
871 * STUN. See https://trac.pjsip.org/repos/ticket/1412
872 */
873 char target_buf[PJSIP_MAX_URL_SIZE];
874 pj_str_t target;
875 pjsip_host_port via_addr;
876 const void *via_tp;
877
878 target.ptr = target_buf;
879 target.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
880 dlg->target,
881 target_buf, sizeof(target_buf));
882 if (target.slen < 0) target.slen = 0;
883
884 if (pjsua_acc_get_uac_addr(acc_id, dlg->pool, &target,
885 &via_addr, NULL, NULL,
886 &via_tp) == PJ_SUCCESS)
887 {
888 pjsip_dlg_set_via_sent_by(dlg, &via_addr,
889 (pjsip_transport*)via_tp);
890 }
891 }
892
893 /* Set credentials and preference. */
894 pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred);
895 pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
896
897 /* Init callback: */
898 pj_bzero(&pres_cb, sizeof(pres_cb));
899 pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
900
901 /* Create server presence subscription: */
902 status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
903 if (status != PJ_SUCCESS) {
904 int code = PJSIP_ERRNO_TO_SIP_STATUS(status);
905 pjsip_tx_data *tdata;
906
907 pjsua_perror(THIS_FILE, "Unable to create server subscription",
908 status);
909
910 if (code==599 || code > 699 || code < 300) {
911 code = 400;
912 }
913
914 status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata);
915 if (status == PJ_SUCCESS) {
916 status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
917 tdata);
918 }
919
920 PJSUA_UNLOCK();
921 pj_log_pop_indent();
922 return PJ_TRUE;
923 }
924
925 /* If account is locked to specific transport, then lock dialog
926 * to this transport too.
927 */
928 if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
929 pjsip_tpselector tp_sel;
930
931 pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
932 pjsip_dlg_set_transport(dlg, &tp_sel);
933 }
934
935 /* Attach our data to the subscription: */
936 uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres);
937 uapres->sub = sub;
938 uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
939 uapres->acc_id = acc_id;
940 uapres->dlg = dlg;
941 status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
942 uapres->remote, PJSIP_MAX_URL_SIZE);
943 if (status < 1)
944 pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
945 else
946 uapres->remote[status] = '\0';
947
948 pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list);
949 pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres);
950
951 /* Add server subscription to the list: */
952 pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
953
954
955 /* Capture the value of Expires header. */
956 expires_hdr = (pjsip_expires_hdr*)
957 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
958 NULL);
959 if (expires_hdr)
960 uapres->expires = expires_hdr->ivalue;
961 else
962 uapres->expires = -1;
963
964 st_code = (pjsip_status_code)200;
965 reason = pj_str("OK");
966 pjsua_msg_data_init(&msg_data);
967
968 /* Notify application callback, if any */
969 if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
970 pjsua_buddy_id buddy_id;
971
972 buddy_id = find_buddy(rdata->msg_info.from->uri);
973
974 (*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
975 &dlg->remote.info_str,
976 rdata, &st_code, &reason,
977 &msg_data);
978 }
979
980 /* Handle rejection case */
981 if (st_code >= 300) {
982 pjsip_tx_data *tdata;
983
984 /* Create response */
985 status = pjsip_dlg_create_response(dlg, rdata, st_code,
986 &reason, &tdata);
987 if (status != PJ_SUCCESS) {
988 pjsua_perror(THIS_FILE, "Error creating response", status);
989 pj_list_erase(uapres);
990 pjsip_pres_terminate(sub, PJ_FALSE);
991 PJSUA_UNLOCK();
992 pj_log_pop_indent();
993 return PJ_FALSE;
994 }
995
996 /* Add header list, if any */
997 pjsua_process_msg_data(tdata, &msg_data);
998
999 /* Send the response */
1000 status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
1001 tdata);
1002 if (status != PJ_SUCCESS) {
1003 pjsua_perror(THIS_FILE, "Error sending response", status);
1004 /* This is not fatal */
1005 }
1006
1007 /* Terminate presence subscription */
1008 pj_list_erase(uapres);
1009 pjsip_pres_terminate(sub, PJ_FALSE);
1010 PJSUA_UNLOCK();
1011 pj_log_pop_indent();
1012 return PJ_TRUE;
1013 }
1014
1015 /* Create and send 2xx response to the SUBSCRIBE request: */
1016 status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
1017 if (status != PJ_SUCCESS) {
1018 pjsua_perror(THIS_FILE, "Unable to accept presence subscription",
1019 status);
1020 pj_list_erase(uapres);
1021 pjsip_pres_terminate(sub, PJ_FALSE);
1022 PJSUA_UNLOCK();
1023 pj_log_pop_indent();
1024 return PJ_FALSE;
1025 }
1026
1027 /* If code is 200, send NOTIFY now */
1028 if (st_code == 200) {
1029 pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE,
1030 NULL, NULL, PJ_TRUE, &msg_data);
1031 }
1032
1033 /* Done: */
1034
1035 PJSUA_UNLOCK();
1036 pj_log_pop_indent();
1037 return PJ_TRUE;
1038}
1039
1040
1041/*
1042 * Send NOTIFY.
1043 */
1044PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id,
1045 pjsua_srv_pres *srv_pres,
1046 pjsip_evsub_state ev_state,
1047 const pj_str_t *state_str,
1048 const pj_str_t *reason,
1049 pj_bool_t with_body,
1050 const pjsua_msg_data *msg_data)
1051{
1052 pjsua_acc *acc;
1053 pjsip_pres_status pres_status;
1054 pjsua_buddy_id buddy_id;
1055 pjsip_tx_data *tdata;
1056 pj_status_t status;
1057
1058 /* Check parameters */
1059 PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL);
1060
1061 /* Check that account ID is valid */
1062 PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
1063 PJ_EINVAL);
1064 /* Check that account is valid */
1065 PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
1066
1067 PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..",
1068 acc_id, (int)(pj_ssize_t)srv_pres));
1069 pj_log_push_indent();
1070
1071 PJSUA_LOCK();
1072
1073 acc = &pjsua_var.acc[acc_id];
1074
1075 /* Check that the server presence subscription is still valid */
1076 if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) {
1077 /* Subscription has been terminated */
1078 PJSUA_UNLOCK();
1079 pj_log_pop_indent();
1080 return PJ_EINVALIDOP;
1081 }
1082
1083 /* Set our online status: */
1084 pj_bzero(&pres_status, sizeof(pres_status));
1085 pres_status.info_cnt = 1;
1086 pres_status.info[0].basic_open = acc->online_status;
1087 pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1088 //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
1089 //causing XML parsing to fail.
1090 //pres_status.info[0].contact = pjsua_var.local_uri;
1091 /* add RPID information */
1092 pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1093 sizeof(pjrpid_element));
1094
1095 pjsip_pres_set_status(srv_pres->sub, &pres_status);
1096
1097 /* Check expires value. If it's zero, send our presense state but
1098 * set subscription state to TERMINATED.
1099 */
1100 if (srv_pres->expires == 0)
1101 ev_state = PJSIP_EVSUB_STATE_TERMINATED;
1102
1103 /* Create and send the NOTIFY to active subscription: */
1104 status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str,
1105 reason, &tdata);
1106 if (status == PJ_SUCCESS) {
1107 /* Force removal of message body if msg_body==FALSE */
1108 if (!with_body) {
1109 tdata->msg->body = NULL;
1110 }
1111 pjsua_process_msg_data(tdata, msg_data);
1112 status = pjsip_pres_send_request( srv_pres->sub, tdata);
1113 }
1114
1115 if (status != PJ_SUCCESS) {
1116 pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY",
1117 status);
1118 pj_list_erase(srv_pres);
1119 pjsip_pres_terminate(srv_pres->sub, PJ_FALSE);
1120 PJSUA_UNLOCK();
1121 pj_log_pop_indent();
1122 return status;
1123 }
1124
1125
1126 /* Subscribe to buddy's presence if we're not subscribed */
1127 buddy_id = find_buddy(srv_pres->dlg->remote.info->uri);
1128 if (buddy_id != PJSUA_INVALID_ID) {
1129 pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
1130 if (b->monitor && b->sub == NULL) {
1131 PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, "
1132 "activating outgoing subscription", buddy_id));
1133 subscribe_buddy_presence(buddy_id);
1134 }
1135 }
1136
1137 PJSUA_UNLOCK();
1138 pj_log_pop_indent();
1139 return PJ_SUCCESS;
1140}
1141
1142
1143/*
1144 * Client presence publication callback.
1145 */
1146static void publish_cb(struct pjsip_publishc_cbparam *param)
1147{
1148 pjsua_acc *acc = (pjsua_acc*) param->token;
1149
1150 if (param->code/100 != 2 || param->status != PJ_SUCCESS) {
1151
1152 pjsip_publishc_destroy(param->pubc);
1153 acc->publish_sess = NULL;
1154
1155 if (param->status != PJ_SUCCESS) {
1156 char errmsg[PJ_ERR_MSG_SIZE];
1157
1158 pj_strerror(param->status, errmsg, sizeof(errmsg));
1159 PJ_LOG(1,(THIS_FILE,
1160 "Client publication (PUBLISH) failed, status=%d, msg=%s",
1161 param->status, errmsg));
1162 } else if (param->code == 412) {
1163 /* 412 (Conditional Request Failed)
1164 * The PUBLISH refresh has failed, retry with new one.
1165 */
1166 pjsua_pres_init_publish_acc(acc->index);
1167
1168 } else {
1169 PJ_LOG(1,(THIS_FILE,
1170 "Client publication (PUBLISH) failed (%d/%.*s)",
1171 param->code, (int)param->reason.slen,
1172 param->reason.ptr));
1173 }
1174
1175 } else {
1176 if (param->expiration < 1) {
1177 /* Could happen if server "forgot" to include Expires header
1178 * in the response. We will not renew, so destroy the pubc.
1179 */
1180 pjsip_publishc_destroy(param->pubc);
1181 acc->publish_sess = NULL;
1182 }
1183 }
1184}
1185
1186
1187/*
1188 * Send PUBLISH request.
1189 */
1190static pj_status_t send_publish(int acc_id, pj_bool_t active)
1191{
1192 pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1193 pjsua_acc *acc = &pjsua_var.acc[acc_id];
1194 pjsip_pres_status pres_status;
1195 pjsip_tx_data *tdata;
1196 pj_status_t status;
1197
1198 PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..",
1199 acc_id, (active ? "" : "un-")));
1200 pj_log_push_indent();
1201
1202 /* Create PUBLISH request */
1203 if (active) {
1204 char *bpos;
1205 pj_str_t entity;
1206
1207 status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata);
1208 if (status != PJ_SUCCESS) {
1209 pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1210 goto on_error;
1211 }
1212
1213 /* Set our online status: */
1214 pj_bzero(&pres_status, sizeof(pres_status));
1215 pres_status.info_cnt = 1;
1216 pres_status.info[0].basic_open = acc->online_status;
1217 pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1218 /* .. including RPID information */
1219 pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1220 sizeof(pjrpid_element));
1221
1222 /* Be careful not to send PIDF with presence entity ID containing
1223 * "<" character.
1224 */
1225 if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) {
1226 char *epos = pj_strchr(&acc_cfg->id, '>');
1227 if (epos - bpos < 2) {
1228 pj_assert(!"Unexpected invalid URI");
1229 status = PJSIP_EINVALIDURI;
1230 goto on_error;
1231 }
1232 entity.ptr = bpos+1;
1233 entity.slen = epos - bpos - 1;
1234 } else {
1235 entity = acc_cfg->id;
1236 }
1237
1238 /* Create and add PIDF message body */
1239 status = pjsip_pres_create_pidf(tdata->pool, &pres_status,
1240 &entity, &tdata->msg->body);
1241 if (status != PJ_SUCCESS) {
1242 pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request",
1243 status);
1244 pjsip_tx_data_dec_ref(tdata);
1245 goto on_error;
1246 }
1247 } else {
1248 status = pjsip_publishc_unpublish(acc->publish_sess, &tdata);
1249 if (status != PJ_SUCCESS) {
1250 pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1251 goto on_error;
1252 }
1253 }
1254
1255 /* Add headers etc */
1256 pjsua_process_msg_data(tdata, NULL);
1257
1258 /* Set Via sent-by */
1259 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
1260 pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr,
1261 acc->via_tp);
1262 } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
1263 /* Choose local interface to use in Via if acc is not using
1264 * STUN. See https://trac.pjsip.org/repos/ticket/1412
1265 */
1266 pjsip_host_port via_addr;
1267 const void *via_tp;
1268
1269 if (pjsua_acc_get_uac_addr(acc_id, acc->pool, &acc_cfg->id,
1270 &via_addr, NULL, NULL,
1271 &via_tp) == PJ_SUCCESS)
1272 {
1273 pjsip_publishc_set_via_sent_by(acc->publish_sess, &via_addr,
1274 (pjsip_transport*)via_tp);
1275 }
1276 }
1277
1278 /* Send the PUBLISH request */
1279 status = pjsip_publishc_send(acc->publish_sess, tdata);
1280 if (status == PJ_EPENDING) {
1281 PJ_LOG(3,(THIS_FILE, "Previous request is in progress, "
1282 "PUBLISH request is queued"));
1283 } else if (status != PJ_SUCCESS) {
1284 pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status);
1285 goto on_error;
1286 }
1287
1288 acc->publish_state = acc->online_status;
1289 pj_log_pop_indent();
1290 return PJ_SUCCESS;
1291
1292on_error:
1293 if (acc->publish_sess) {
1294 pjsip_publishc_destroy(acc->publish_sess);
1295 acc->publish_sess = NULL;
1296 }
1297 pj_log_pop_indent();
1298 return status;
1299}
1300
1301
1302/* Create client publish session */
1303pj_status_t pjsua_pres_init_publish_acc(int acc_id)
1304{
1305 const pj_str_t STR_PRESENCE = { "presence", 8 };
1306 pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1307 pjsua_acc *acc = &pjsua_var.acc[acc_id];
1308 pj_status_t status;
1309
1310 /* Create and init client publication session */
1311 if (acc_cfg->publish_enabled) {
1312
1313 /* Create client publication */
1314 status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt,
1315 acc, &publish_cb,
1316 &acc->publish_sess);
1317 if (status != PJ_SUCCESS) {
1318 acc->publish_sess = NULL;
1319 return status;
1320 }
1321
1322 /* Initialize client publication */
1323 status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE,
1324 &acc_cfg->id, &acc_cfg->id,
1325 &acc_cfg->id,
1326 PJSUA_PUBLISH_EXPIRATION);
1327 if (status != PJ_SUCCESS) {
1328 acc->publish_sess = NULL;
1329 return status;
1330 }
1331
1332 /* Add credential for authentication */
1333 if (acc->cred_cnt) {
1334 pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt,
1335 acc->cred);
1336 }
1337
1338 /* Set route-set */
1339 pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set);
1340
1341 /* Send initial PUBLISH request */
1342 if (acc->online_status != 0) {
1343 status = send_publish(acc_id, PJ_TRUE);
1344 if (status != PJ_SUCCESS)
1345 return status;
1346 }
1347
1348 } else {
1349 acc->publish_sess = NULL;
1350 }
1351
1352 return PJ_SUCCESS;
1353}
1354
1355
1356/* Init presence for account */
1357pj_status_t pjsua_pres_init_acc(int acc_id)
1358{
1359 pjsua_acc *acc = &pjsua_var.acc[acc_id];
1360
1361 /* Init presence subscription */
1362 pj_list_init(&acc->pres_srv_list);
1363
1364 return PJ_SUCCESS;
1365}
1366
1367
1368/* Unpublish presence publication */
1369void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
1370{
1371 if (acc->publish_sess) {
1372 pjsua_acc_config *acc_cfg = &acc->cfg;
1373
1374 acc->online_status = PJ_FALSE;
1375
1376 if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1377 send_publish(acc->index, PJ_FALSE);
1378 }
1379
1380 /* By ticket #364, don't destroy the session yet (let the callback
1381 destroy it)
1382 if (acc->publish_sess) {
1383 pjsip_publishc_destroy(acc->publish_sess);
1384 acc->publish_sess = NULL;
1385 }
1386 */
1387 acc_cfg->publish_enabled = PJ_FALSE;
1388 }
1389}
1390
1391/* Terminate server subscription for the account */
1392void pjsua_pres_delete_acc(int acc_id, unsigned flags)
1393{
1394 pjsua_acc *acc = &pjsua_var.acc[acc_id];
1395 pjsua_srv_pres *uapres;
1396
1397 uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1398
1399 /* Notify all subscribers that we're no longer available */
1400 while (uapres != &acc->pres_srv_list) {
1401
1402 pjsip_pres_status pres_status;
1403 pj_str_t reason = { "noresource", 10 };
1404 pjsua_srv_pres *next;
1405 pjsip_tx_data *tdata;
1406
1407 next = uapres->next;
1408
1409 pjsip_pres_get_status(uapres->sub, &pres_status);
1410
1411 pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
1412 pjsip_pres_set_status(uapres->sub, &pres_status);
1413
1414 if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1415 if (pjsip_pres_notify(uapres->sub,
1416 PJSIP_EVSUB_STATE_TERMINATED, NULL,
1417 &reason, &tdata)==PJ_SUCCESS)
1418 {
1419 pjsip_pres_send_request(uapres->sub, tdata);
1420 }
1421 } else {
1422 pjsip_pres_terminate(uapres->sub, PJ_FALSE);
1423 }
1424
1425 uapres = next;
1426 }
1427
1428 /* Clear server presence subscription list because account might be reused
1429 * later. */
1430 pj_list_init(&acc->pres_srv_list);
1431
1432 /* Terminate presence publication, if any */
1433 pjsua_pres_unpublish(acc, flags);
1434}
1435
1436
1437/* Update server subscription (e.g. when our online status has changed) */
1438void pjsua_pres_update_acc(int acc_id, pj_bool_t force)
1439{
1440 pjsua_acc *acc = &pjsua_var.acc[acc_id];
1441 pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1442 pjsua_srv_pres *uapres;
1443
1444 uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1445
1446 while (uapres != &acc->pres_srv_list) {
1447
1448 pjsip_pres_status pres_status;
1449 pjsip_tx_data *tdata;
1450
1451 pjsip_pres_get_status(uapres->sub, &pres_status);
1452
1453 /* Only send NOTIFY once subscription is active. Some subscriptions
1454 * may still be in NULL (when app is adding a new buddy while in the
1455 * on_incoming_subscribe() callback) or PENDING (when user approval is
1456 * being requested) state and we don't send NOTIFY to these subs until
1457 * the user accepted the request.
1458 */
1459 if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE &&
1460 (force || pres_status.info[0].basic_open != acc->online_status))
1461 {
1462
1463 pres_status.info[0].basic_open = acc->online_status;
1464 pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1465 sizeof(pjrpid_element));
1466
1467 pjsip_pres_set_status(uapres->sub, &pres_status);
1468
1469 if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) {
1470 pjsua_process_msg_data(tdata, NULL);
1471 pjsip_pres_send_request(uapres->sub, tdata);
1472 }
1473 }
1474
1475 uapres = uapres->next;
1476 }
1477
1478 /* Send PUBLISH if required. We only do this when we have a PUBLISH
1479 * session. If we don't have a PUBLISH session, then it could be
1480 * that we're waiting until registration has completed before we
1481 * send the first PUBLISH.
1482 */
1483 if (acc_cfg->publish_enabled && acc->publish_sess) {
1484 if (force || acc->publish_state != acc->online_status) {
1485 send_publish(acc_id, PJ_TRUE);
1486 }
1487 }
1488}
1489
1490
1491
1492/***************************************************************************
1493 * Client subscription.
1494 */
1495
1496static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry)
1497{
1498 pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data;
1499
1500 PJ_UNUSED_ARG(th);
1501
1502 entry->id = PJ_FALSE;
1503 pjsua_buddy_update_pres(buddy->index);
1504}
1505
1506/* Reschedule subscription refresh timer or terminate the subscription
1507 * refresh timer for the specified buddy.
1508 */
1509static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched,
1510 unsigned msec_interval)
1511{
1512 if (buddy->timer.id) {
1513 pjsua_cancel_timer(&buddy->timer);
1514 buddy->timer.id = PJ_FALSE;
1515 }
1516
1517 if (resched) {
1518 pj_time_val delay;
1519
1520 PJ_LOG(4,(THIS_FILE,
1521 "Resubscribing buddy id %u in %u ms (reason: %.*s)",
1522 buddy->index, msec_interval,
1523 (int)buddy->term_reason.slen,
1524 buddy->term_reason.ptr));
1525
1526 pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb);
1527 delay.sec = 0;
1528 delay.msec = msec_interval;
1529 pj_time_val_normalize(&delay);
1530
1531 if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS)
1532 buddy->timer.id = PJ_TRUE;
1533 }
1534}
1535
1536/* Callback called when *client* subscription state has changed. */
1537static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
1538{
1539 pjsua_buddy *buddy;
1540
1541 PJ_UNUSED_ARG(event);
1542
1543 /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1544 * a dialog attached to it, lock_buddy() will use the dialog
1545 * lock, which we are currently holding!
1546 */
1547 buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1548 if (buddy) {
1549 PJ_LOG(4,(THIS_FILE,
1550 "Presence subscription to %.*s is %s",
1551 (int)pjsua_var.buddy[buddy->index].uri.slen,
1552 pjsua_var.buddy[buddy->index].uri.ptr,
1553 pjsip_evsub_get_state_name(sub)));
1554 pj_log_push_indent();
1555
1556 if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1557 int resub_delay = -1;
1558
1559 if (buddy->term_reason.ptr == NULL) {
1560 buddy->term_reason.ptr = (char*)
1561 pj_pool_alloc(buddy->pool,
1562 PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1563 }
1564 pj_strncpy(&buddy->term_reason,
1565 pjsip_evsub_get_termination_reason(sub),
1566 PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1567
1568 buddy->term_code = 200;
1569
1570 /* Determine whether to resubscribe automatically */
1571 if (event && event->type==PJSIP_EVENT_TSX_STATE) {
1572 const pjsip_transaction *tsx = event->body.tsx_state.tsx;
1573 if (pjsip_method_cmp(&tsx->method,
1574 &pjsip_subscribe_method)==0)
1575 {
1576 buddy->term_code = tsx->status_code;
1577 switch (tsx->status_code) {
1578 case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
1579 /* 481: we refreshed too late? resubscribe
1580 * immediately.
1581 */
1582 /* But this must only happen when the 481 is received
1583 * on subscription refresh request. We MUST NOT try to
1584 * resubscribe automatically if the 481 is received
1585 * on the initial SUBSCRIBE (if server returns this
1586 * response for some reason).
1587 */
1588 if (buddy->dlg->remote.contact)
1589 resub_delay = 500;
1590 break;
1591 }
1592 } else if (pjsip_method_cmp(&tsx->method,
1593 &pjsip_notify_method)==0)
1594 {
1595 if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 ||
1596 pj_stricmp2(&buddy->term_reason, "timeout")==0) {
1597 /* deactivated: The subscription has been terminated,
1598 * but the subscriber SHOULD retry immediately with
1599 * a new subscription.
1600 */
1601 /* timeout: The subscription has been terminated
1602 * because it was not refreshed before it expired.
1603 * Clients MAY re-subscribe immediately. The
1604 * "retry-after" parameter has no semantics for
1605 * "timeout".
1606 */
1607 resub_delay = 500;
1608 }
1609 else if (pj_stricmp2(&buddy->term_reason, "probation")==0||
1610 pj_stricmp2(&buddy->term_reason, "giveup")==0) {
1611 /* probation: The subscription has been terminated,
1612 * but the client SHOULD retry at some later time.
1613 * If a "retry-after" parameter is also present, the
1614 * client SHOULD wait at least the number of seconds
1615 * specified by that parameter before attempting to re-
1616 * subscribe.
1617 */
1618 /* giveup: The subscription has been terminated because
1619 * the notifier could not obtain authorization in a
1620 * timely fashion. If a "retry-after" parameter is
1621 * also present, the client SHOULD wait at least the
1622 * number of seconds specified by that parameter before
1623 * attempting to re-subscribe; otherwise, the client
1624 * MAY retry immediately, but will likely get put back
1625 * into pending state.
1626 */
1627 const pjsip_sub_state_hdr *sub_hdr;
1628 pj_str_t sub_state = { "Subscription-State", 18 };
1629 const pjsip_msg *msg;
1630
1631 msg = event->body.tsx_state.src.rdata->msg_info.msg;
1632 sub_hdr = (const pjsip_sub_state_hdr*)
1633 pjsip_msg_find_hdr_by_name(msg, &sub_state,
1634 NULL);
1635 if (sub_hdr && sub_hdr->retry_after > 0)
1636 resub_delay = sub_hdr->retry_after * 1000;
1637 }
1638
1639 }
1640 }
1641
1642 /* For other cases of subscription termination, if resubscribe
1643 * timer is not set, schedule with default expiration (plus minus
1644 * some random value, to avoid sending SUBSCRIBEs all at once)
1645 */
1646 if (resub_delay == -1) {
1647 pj_assert(PJSUA_PRES_TIMER >= 3);
1648 resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000);
1649 }
1650
1651 buddy_resubscribe(buddy, PJ_TRUE, resub_delay);
1652
1653 } else {
1654 /* This will clear the last termination code/reason */
1655 buddy->term_code = 0;
1656 buddy->term_reason.slen = 0;
1657 }
1658
1659 /* Call callbacks */
1660 if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state)
1661 (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub,
1662 event);
1663
1664 if (pjsua_var.ua_cfg.cb.on_buddy_state)
1665 (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
1666
1667 /* Clear subscription */
1668 if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1669 buddy->sub = NULL;
1670 buddy->status.info_cnt = 0;
1671 buddy->dlg = NULL;
1672 pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
1673 }
1674
1675 pj_log_pop_indent();
1676 }
1677}
1678
1679
1680/* Callback when transaction state has changed. */
1681static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub,
1682 pjsip_transaction *tsx,
1683 pjsip_event *event)
1684{
1685 pjsua_buddy *buddy;
1686 pjsip_contact_hdr *contact_hdr;
1687
1688 /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1689 * a dialog attached to it, lock_buddy() will use the dialog
1690 * lock, which we are currently holding!
1691 */
1692 buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1693 if (!buddy) {
1694 return;
1695 }
1696
1697 /* We only use this to update buddy's Contact, when it's not
1698 * set.
1699 */
1700 if (buddy->contact.slen != 0) {
1701 /* Contact already set */
1702 return;
1703 }
1704
1705 /* Only care about 2xx response to outgoing SUBSCRIBE */
1706 if (tsx->status_code/100 != 2 ||
1707 tsx->role != PJSIP_UAC_ROLE ||
1708 event->type != PJSIP_EVENT_RX_MSG ||
1709 pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0)
1710 {
1711 return;
1712 }
1713
1714 /* Find contact header. */
1715 contact_hdr = (pjsip_contact_hdr*)
1716 pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
1717 PJSIP_H_CONTACT, NULL);
1718 if (!contact_hdr || !contact_hdr->uri) {
1719 return;
1720 }
1721
1722 buddy->contact.ptr = (char*)
1723 pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE);
1724 buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
1725 contact_hdr->uri,
1726 buddy->contact.ptr,
1727 PJSIP_MAX_URL_SIZE);
1728 if (buddy->contact.slen < 0)
1729 buddy->contact.slen = 0;
1730}
1731
1732
1733/* Callback called when we receive NOTIFY */
1734static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
1735 pjsip_rx_data *rdata,
1736 int *p_st_code,
1737 pj_str_t **p_st_text,
1738 pjsip_hdr *res_hdr,
1739 pjsip_msg_body **p_body)
1740{
1741 pjsua_buddy *buddy;
1742
1743 /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1744 * a dialog attached to it, lock_buddy() will use the dialog
1745 * lock, which we are currently holding!
1746 */
1747 buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1748 if (buddy) {
1749 /* Update our info. */
1750 pjsip_pres_get_status(sub, &buddy->status);
1751 }
1752
1753 /* The default is to send 200 response to NOTIFY.
1754 * Just leave it there..
1755 */
1756 PJ_UNUSED_ARG(rdata);
1757 PJ_UNUSED_ARG(p_st_code);
1758 PJ_UNUSED_ARG(p_st_text);
1759 PJ_UNUSED_ARG(res_hdr);
1760 PJ_UNUSED_ARG(p_body);
1761}
1762
1763
1764/* It does what it says.. */
1765static void subscribe_buddy_presence(pjsua_buddy_id buddy_id)
1766{
1767 pjsip_evsub_user pres_callback;
1768 pj_pool_t *tmp_pool = NULL;
1769 pjsua_buddy *buddy;
1770 int acc_id;
1771 pjsua_acc *acc;
1772 pj_str_t contact;
1773 pjsip_tx_data *tdata;
1774 pj_status_t status;
1775
1776 /* Event subscription callback. */
1777 pj_bzero(&pres_callback, sizeof(pres_callback));
1778 pres_callback.on_evsub_state = &pjsua_evsub_on_state;
1779 pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state;
1780 pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify;
1781
1782 buddy = &pjsua_var.buddy[buddy_id];
1783 acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
1784
1785 acc = &pjsua_var.acc[acc_id];
1786
1787 PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..",
1788 buddy_id, acc_id));
1789 pj_log_push_indent();
1790
1791 /* Generate suitable Contact header unless one is already set in
1792 * the account
1793 */
1794 if (acc->contact.slen) {
1795 contact = acc->contact;
1796 } else {
1797 tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256);
1798
1799 status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
1800 acc_id, &buddy->uri);
1801 if (status != PJ_SUCCESS) {
1802 pjsua_perror(THIS_FILE, "Unable to generate Contact header",
1803 status);
1804 pj_pool_release(tmp_pool);
1805 pj_log_pop_indent();
1806 return;
1807 }
1808 }
1809
1810 /* Create UAC dialog */
1811 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
1812 &acc->cfg.id,
1813 &contact,
1814 &buddy->uri,
1815 NULL, &buddy->dlg);
1816 if (status != PJ_SUCCESS) {
1817 pjsua_perror(THIS_FILE, "Unable to create dialog",
1818 status);
1819 if (tmp_pool) pj_pool_release(tmp_pool);
1820 pj_log_pop_indent();
1821 return;
1822 }
1823
1824 /* Increment the dialog's lock otherwise when presence session creation
1825 * fails the dialog will be destroyed prematurely.
1826 */
1827 pjsip_dlg_inc_lock(buddy->dlg);
1828
1829 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
1830 pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp);
1831 } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
1832 /* Choose local interface to use in Via if acc is not using
1833 * STUN. See https://trac.pjsip.org/repos/ticket/1412
1834 */
1835 pjsip_host_port via_addr;
1836 const void *via_tp;
1837
1838 if (pjsua_acc_get_uac_addr(acc_id, buddy->dlg->pool, &buddy->uri,
1839 &via_addr, NULL, NULL,
1840 &via_tp) == PJ_SUCCESS)
1841 {
1842 pjsip_dlg_set_via_sent_by(buddy->dlg, &via_addr,
1843 (pjsip_transport*)via_tp);
1844 }
1845 }
1846
1847
1848 status = pjsip_pres_create_uac( buddy->dlg, &pres_callback,
1849 PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub);
1850 if (status != PJ_SUCCESS) {
1851 buddy->sub = NULL;
1852 pjsua_perror(THIS_FILE, "Unable to create presence client",
1853 status);
1854 /* This should destroy the dialog since there's no session
1855 * referencing it
1856 */
1857 if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1858 if (tmp_pool) pj_pool_release(tmp_pool);
1859 pj_log_pop_indent();
1860 return;
1861 }
1862
1863 /* If account is locked to specific transport, then lock dialog
1864 * to this transport too.
1865 */
1866 if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
1867 pjsip_tpselector tp_sel;
1868
1869 pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
1870 pjsip_dlg_set_transport(buddy->dlg, &tp_sel);
1871 }
1872
1873 /* Set route-set */
1874 if (!pj_list_empty(&acc->route_set)) {
1875 pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set);
1876 }
1877
1878 /* Set credentials */
1879 if (acc->cred_cnt) {
1880 pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess,
1881 acc->cred_cnt, acc->cred);
1882 }
1883
1884 /* Set authentication preference */
1885 pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref);
1886
1887 pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
1888
1889 status = pjsip_pres_initiate(buddy->sub, -1, &tdata);
1890 if (status != PJ_SUCCESS) {
1891 if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1892 if (buddy->sub) {
1893 pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1894 }
1895 buddy->sub = NULL;
1896 pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE",
1897 status);
1898 if (tmp_pool) pj_pool_release(tmp_pool);
1899 pj_log_pop_indent();
1900 return;
1901 }
1902
1903 pjsua_process_msg_data(tdata, NULL);
1904
1905 status = pjsip_pres_send_request(buddy->sub, tdata);
1906 if (status != PJ_SUCCESS) {
1907 if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1908 if (buddy->sub) {
1909 pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1910 }
1911 buddy->sub = NULL;
1912 pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE",
1913 status);
1914 if (tmp_pool) pj_pool_release(tmp_pool);
1915 pj_log_pop_indent();
1916 return;
1917 }
1918
1919 pjsip_dlg_dec_lock(buddy->dlg);
1920 if (tmp_pool) pj_pool_release(tmp_pool);
1921 pj_log_pop_indent();
1922}
1923
1924
1925/* It does what it says... */
1926static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)
1927{
1928 pjsua_buddy *buddy;
1929 pjsip_tx_data *tdata;
1930 pj_status_t status;
1931
1932 buddy = &pjsua_var.buddy[buddy_id];
1933
1934 if (buddy->sub == NULL)
1935 return;
1936
1937 if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1938 buddy->sub = NULL;
1939 return;
1940 }
1941
1942 PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id));
1943 pj_log_push_indent();
1944
1945 status = pjsip_pres_initiate( buddy->sub, 0, &tdata);
1946 if (status == PJ_SUCCESS) {
1947 pjsua_process_msg_data(tdata, NULL);
1948 status = pjsip_pres_send_request( buddy->sub, tdata );
1949 }
1950
1951 if (status != PJ_SUCCESS && buddy->sub) {
1952 pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1953 buddy->sub = NULL;
1954 pjsua_perror(THIS_FILE, "Unable to unsubscribe presence",
1955 status);
1956 }
1957
1958 pj_log_pop_indent();
1959}
1960
1961/* It does what it says.. */
1962static pj_status_t refresh_client_subscriptions(void)
1963{
1964 unsigned i;
1965 pj_status_t status;
1966
1967 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
1968 struct buddy_lock lck;
1969
1970 if (!pjsua_buddy_is_valid(i))
1971 continue;
1972
1973 status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0);
1974 if (status != PJ_SUCCESS)
1975 return status;
1976
1977 if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
1978 subscribe_buddy_presence(i);
1979
1980 } else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) {
1981 unsubscribe_buddy_presence(i);
1982
1983 }
1984
1985 unlock_buddy(&lck);
1986 }
1987
1988 return PJ_SUCCESS;
1989}
1990
1991/***************************************************************************
1992 * MWI
1993 */
1994/* Callback called when *client* subscription state has changed. */
1995static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
1996{
1997 pjsua_acc *acc;
1998
1999 PJ_UNUSED_ARG(event);
2000
2001 /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
2002 * a dialog attached to it, lock_buddy() will use the dialog
2003 * lock, which we are currently holding!
2004 */
2005 acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
2006 if (!acc)
2007 return;
2008
2009 PJ_LOG(4,(THIS_FILE,
2010 "MWI subscription for %.*s is %s",
2011 (int)acc->cfg.id.slen, acc->cfg.id.ptr,
2012 pjsip_evsub_get_state_name(sub)));
2013
2014 /* Call callback */
2015 if (pjsua_var.ua_cfg.cb.on_mwi_state) {
2016 (*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub);
2017 }
2018
2019 if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
2020 /* Clear subscription */
2021 acc->mwi_dlg = NULL;
2022 acc->mwi_sub = NULL;
2023 pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
2024
2025 }
2026}
2027
2028/* Callback called when we receive NOTIFY */
2029static void mwi_evsub_on_rx_notify(pjsip_evsub *sub,
2030 pjsip_rx_data *rdata,
2031 int *p_st_code,
2032 pj_str_t **p_st_text,
2033 pjsip_hdr *res_hdr,
2034 pjsip_msg_body **p_body)
2035{
2036 pjsua_mwi_info mwi_info;
2037 pjsua_acc *acc;
2038
2039 PJ_UNUSED_ARG(p_st_code);
2040 PJ_UNUSED_ARG(p_st_text);
2041 PJ_UNUSED_ARG(res_hdr);
2042 PJ_UNUSED_ARG(p_body);
2043
2044 acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
2045 if (!acc)
2046 return;
2047
2048 /* Construct mwi_info */
2049 pj_bzero(&mwi_info, sizeof(mwi_info));
2050 mwi_info.evsub = sub;
2051 mwi_info.rdata = rdata;
2052
2053 PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY.."));
2054 pj_log_push_indent();
2055
2056 /* Call callback */
2057 if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2058 (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info);
2059 }
2060
2061 pj_log_pop_indent();
2062}
2063
2064
2065/* Event subscription callback. */
2066static pjsip_evsub_user mwi_cb =
2067{
2068 &mwi_evsub_on_state,
2069 NULL, /* on_tsx_state: not interested */
2070 NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
2071 * we want to authenticate
2072 */
2073
2074 &mwi_evsub_on_rx_notify,
2075
2076 NULL, /* on_client_refresh: Use default behaviour, which is to
2077 * refresh client subscription. */
2078
2079 NULL, /* on_server_timeout: Use default behaviour, which is to send
2080 * NOTIFY to terminate.
2081 */
2082};
2083
2084pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew)
2085{
2086 pjsua_acc *acc;
2087 pj_pool_t *tmp_pool = NULL;
2088 pj_str_t contact;
2089 pjsip_tx_data *tdata;
2090 pj_status_t status = PJ_SUCCESS;
2091
2092 PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc)
2093 && pjsua_var.acc[acc_id].valid, PJ_EINVAL);
2094
2095 acc = &pjsua_var.acc[acc_id];
2096
2097 if (!acc->cfg.mwi_enabled || !acc->regc) {
2098 if (acc->mwi_sub) {
2099 /* Terminate MWI subscription */
2100 pjsip_evsub *sub = acc->mwi_sub;
2101
2102 /* Detach sub from this account */
2103 acc->mwi_sub = NULL;
2104 acc->mwi_dlg = NULL;
2105 pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
2106
2107 /* Unsubscribe */
2108 status = pjsip_mwi_initiate(sub, 0, &tdata);
2109 if (status == PJ_SUCCESS) {
2110 status = pjsip_mwi_send_request(sub, tdata);
2111 }
2112 }
2113 return status;
2114 }
2115
2116 /* Subscription is already active */
2117 if (acc->mwi_sub) {
2118 if (!force_renew)
2119 return PJ_SUCCESS;
2120
2121 /* Update MWI subscription */
2122 pj_assert(acc->mwi_dlg);
2123 pjsip_dlg_inc_lock(acc->mwi_dlg);
2124
2125 status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2126 if (status == PJ_SUCCESS) {
2127 pjsua_process_msg_data(tdata, NULL);
2128 status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2129 }
2130
2131 pjsip_dlg_dec_lock(acc->mwi_dlg);
2132 return status;
2133 }
2134
2135 PJ_LOG(4,(THIS_FILE, "Starting MWI subscription.."));
2136 pj_log_push_indent();
2137
2138 /* Generate suitable Contact header unless one is already set in
2139 * the account
2140 */
2141 if (acc->contact.slen) {
2142 contact = acc->contact;
2143 } else {
2144 tmp_pool = pjsua_pool_create("tmpmwi", 512, 256);
2145 status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
2146 acc->index, &acc->cfg.id);
2147 if (status != PJ_SUCCESS) {
2148 pjsua_perror(THIS_FILE, "Unable to generate Contact header",
2149 status);
2150 goto on_return;
2151 }
2152 }
2153
2154 /* Create UAC dialog */
2155 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
2156 &acc->cfg.id,
2157 &contact,
2158 &acc->cfg.id,
2159 NULL, &acc->mwi_dlg);
2160 if (status != PJ_SUCCESS) {
2161 pjsua_perror(THIS_FILE, "Unable to create dialog", status);
2162 goto on_return;
2163 }
2164
2165 /* Increment the dialog's lock otherwise when presence session creation
2166 * fails the dialog will be destroyed prematurely.
2167 */
2168 pjsip_dlg_inc_lock(acc->mwi_dlg);
2169
2170 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
2171 pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp);
2172 } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
2173 /* Choose local interface to use in Via if acc is not using
2174 * STUN. See https://trac.pjsip.org/repos/ticket/1412
2175 */
2176 pjsip_host_port via_addr;
2177 const void *via_tp;
2178
2179 if (pjsua_acc_get_uac_addr(acc_id, acc->mwi_dlg->pool, &acc->cfg.id,
2180 &via_addr, NULL, NULL,
2181 &via_tp) == PJ_SUCCESS)
2182 {
2183 pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &via_addr,
2184 (pjsip_transport*)via_tp);
2185 }
2186 }
2187
2188 /* Create UAC subscription */
2189 status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb,
2190 PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub);
2191 if (status != PJ_SUCCESS) {
2192 pjsua_perror(THIS_FILE, "Error creating MWI subscription", status);
2193 if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2194 goto on_return;
2195 }
2196
2197 /* If account is locked to specific transport, then lock dialog
2198 * to this transport too.
2199 */
2200 if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
2201 pjsip_tpselector tp_sel;
2202
2203 pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
2204 pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel);
2205 }
2206
2207 /* Set route-set */
2208 if (!pj_list_empty(&acc->route_set)) {
2209 pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set);
2210 }
2211
2212 /* Set credentials */
2213 if (acc->cred_cnt) {
2214 pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess,
2215 acc->cred_cnt, acc->cred);
2216 }
2217
2218 /* Set authentication preference */
2219 pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref);
2220
2221 pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc);
2222
2223 status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2224 if (status != PJ_SUCCESS) {
2225 if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2226 if (acc->mwi_sub) {
2227 pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2228 }
2229 acc->mwi_sub = NULL;
2230 acc->mwi_dlg = NULL;
2231 pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE",
2232 status);
2233 goto on_return;
2234 }
2235
2236 pjsua_process_msg_data(tdata, NULL);
2237
2238 status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2239 if (status != PJ_SUCCESS) {
2240 if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2241 if (acc->mwi_sub) {
2242 pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2243 }
2244 acc->mwi_sub = NULL;
2245 acc->mwi_dlg = NULL;
2246 pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE",
2247 status);
2248 goto on_return;
2249 }
2250
2251 pjsip_dlg_dec_lock(acc->mwi_dlg);
2252
2253on_return:
2254 if (tmp_pool) pj_pool_release(tmp_pool);
2255
2256 pj_log_pop_indent();
2257 return status;
2258}
2259
2260
2261/***************************************************************************
2262 * Unsolicited MWI
2263 */
2264static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata)
2265{
2266 pjsip_msg *msg = rdata->msg_info.msg;
2267 pj_str_t EVENT_HDR = { "Event", 5 };
2268 pj_str_t MWI = { "message-summary", 15 };
2269 pjsip_event_hdr *eh;
2270
2271 if (pjsip_method_cmp(&msg->line.req.method, &pjsip_notify_method)!=0) {
2272 /* Only interested with NOTIFY request */
2273 return PJ_FALSE;
2274 }
2275
2276 eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL);
2277 if (!eh) {
2278 /* Something wrong with the request, it has no Event hdr */
2279 return PJ_FALSE;
2280 }
2281
2282 if (pj_stricmp(&eh->event_type, &MWI) != 0) {
2283 /* Not MWI event */
2284 return PJ_FALSE;
2285 }
2286
2287 PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..",
2288 rdata->pkt_info.src_name, rdata->pkt_info.src_port));
2289 pj_log_push_indent();
2290
2291 /* Got unsolicited MWI request, respond with 200/OK first */
2292 pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL,
2293 NULL, NULL, NULL);
2294
2295
2296 /* Call callback */
2297 if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2298 pjsua_acc_id acc_id;
2299 pjsua_mwi_info mwi_info;
2300
2301 acc_id = pjsua_acc_find_for_incoming(rdata);
2302
2303 pj_bzero(&mwi_info, sizeof(mwi_info));
2304 mwi_info.rdata = rdata;
2305
2306 (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info);
2307 }
2308
2309 pj_log_pop_indent();
2310 return PJ_TRUE;
2311}
2312
2313/* The module instance. */
2314static pjsip_module pjsua_unsolicited_mwi_mod =
2315{
2316 NULL, NULL, /* prev, next. */
2317 { "mod-unsolicited-mwi", 19 }, /* Name. */
2318 -1, /* Id */
2319 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
2320 NULL, /* load() */
2321 NULL, /* start() */
2322 NULL, /* stop() */
2323 NULL, /* unload() */
2324 &unsolicited_mwi_on_rx_request, /* on_rx_request() */
2325 NULL, /* on_rx_response() */
2326 NULL, /* on_tx_request. */
2327 NULL, /* on_tx_response() */
2328 NULL, /* on_tsx_state() */
2329};
2330
2331static pj_status_t enable_unsolicited_mwi(void)
2332{
2333 pj_status_t status;
2334
2335 status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
2336 &pjsua_unsolicited_mwi_mod);
2337 if (status != PJ_SUCCESS)
2338 pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module",
2339 status);
2340
2341 return status;
2342}
2343
2344
2345
2346/***************************************************************************/
2347
2348/* Timer callback to re-create client subscription */
2349static void pres_timer_cb(pj_timer_heap_t *th,
2350 pj_timer_entry *entry)
2351{
2352 unsigned i;
2353 pj_time_val delay = { PJSUA_PRES_TIMER, 0 };
2354
2355 entry->id = PJ_FALSE;
2356
2357 /* Retry failed PUBLISH and MWI SUBSCRIBE requests */
2358 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2359 pjsua_acc *acc = &pjsua_var.acc[i];
2360
2361 /* Acc may not be ready yet, otherwise assertion will happen */
2362 if (!pjsua_acc_is_valid(i))
2363 continue;
2364
2365 /* Retry PUBLISH */
2366 if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
2367 pjsua_pres_init_publish_acc(acc->index);
2368
2369 /* Re-subscribe MWI subscription if it's terminated prematurely */
2370 if (acc->cfg.mwi_enabled && !acc->mwi_sub)
2371 pjsua_start_mwi(acc->index, PJ_FALSE);
2372 }
2373
2374 /* #937: No need to do bulk client refresh, as buddies have their
2375 * own individual timer now.
2376 */
2377 //refresh_client_subscriptions();
2378
2379 pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay);
2380 entry->id = PJ_TRUE;
2381
2382 PJ_UNUSED_ARG(th);
2383}
2384
2385
2386/*
2387 * Init presence
2388 */
2389pj_status_t pjsua_pres_init()
2390{
2391 unsigned i;
2392 pj_status_t status;
2393
2394 status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres);
2395 if (status != PJ_SUCCESS) {
2396 pjsua_perror(THIS_FILE, "Unable to register pjsua presence module",
2397 status);
2398 }
2399
2400 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2401 reset_buddy(i);
2402 }
2403
2404 return status;
2405}
2406
2407
2408/*
2409 * Start presence subsystem.
2410 */
2411pj_status_t pjsua_pres_start(void)
2412{
2413 /* Start presence timer to re-subscribe to buddy's presence when
2414 * subscription has failed.
2415 */
2416 if (pjsua_var.pres_timer.id == PJ_FALSE) {
2417 pj_time_val pres_interval = {PJSUA_PRES_TIMER, 0};
2418
2419 pjsua_var.pres_timer.cb = &pres_timer_cb;
2420 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.pres_timer,
2421 &pres_interval);
2422 pjsua_var.pres_timer.id = PJ_TRUE;
2423 }
2424
2425 if (pjsua_var.ua_cfg.enable_unsolicited_mwi) {
2426 pj_status_t status = enable_unsolicited_mwi();
2427 if (status != PJ_SUCCESS)
2428 return status;
2429 }
2430
2431 return PJ_SUCCESS;
2432}
2433
2434
2435/*
2436 * Shutdown presence.
2437 */
2438void pjsua_pres_shutdown(unsigned flags)
2439{
2440 unsigned i;
2441
2442 PJ_LOG(4,(THIS_FILE, "Shutting down presence.."));
2443 pj_log_push_indent();
2444
2445 if (pjsua_var.pres_timer.id != 0) {
2446 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.pres_timer);
2447 pjsua_var.pres_timer.id = PJ_FALSE;
2448 }
2449
2450 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2451 if (!pjsua_var.acc[i].valid)
2452 continue;
2453 pjsua_pres_delete_acc(i, flags);
2454 }
2455
2456 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2457 pjsua_var.buddy[i].monitor = 0;
2458 }
2459
2460 if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
2461 refresh_client_subscriptions();
2462
2463 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2464 if (pjsua_var.acc[i].valid)
2465 pjsua_pres_update_acc(i, PJ_FALSE);
2466 }
2467 }
2468
2469 pj_log_pop_indent();
2470}