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